F - Two Exams Editorial by physics0523
ひとまず、余計な情報を排除するため、 \(1\) 回目に \(i\) 位だった人が \(2\) 回目に \(A_i\) 位だったというような形に情報を変換します。 例えば入力例1の場合は \(A=(3,2,4,1)\) となります。
このもとで、 \(1\) 回目の順位が良い方から、選ぶか選ばないかを決定していくことを考えます。
\(1\) 回目の順位の昇順に決定することを考えると、「もし今選ぶか選ばないかを決定している人物 \(X\) より前に選ばなかった人物 \(Y\) がいる場合、 \(X\) は \(2\) 回目の試験で \(Y\) よりもよい順位でなければならない」ということが言えます。
このもとで、以下の形式の DP を考えます。
\(dp[\) \(i\) 人目まで選ぶ/選ばないを決定した \(][\) 今までに選んだ人数 \(][\) 選ばなかった人物のうち、最も小さい順位(全員を選んでいる場合は \(\infty\) ) \(]\) = \(\{\) \(i\) 人目まで選ぶ/選ばないを決定した時の選び方の場合の数 \(\}\)
遷移は、各 \(dp[i][j][k]\) から人物 \(i+1\) を選ぶ場合/選ばない場合へと遷移します。(但し、選べない場合もあるので注意してください。)詳しくは実装例を参照してください。(DPの形から遷移を検討するのも練習になるので、できれば自力で考えてみることをおすすめします。)
なお、 \(dp[i+1]\) の計算に \(dp[i]\) 以外を用いることはないので、DPのkeyのうち左端のものは実際に持つ必要はありません。
このDPの計算量は \(O(N^3)\) です。
実装例(C++):
#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
int main(){
int n,k;
cin >> n >> k;
vector<int> p(n),q(n);
for(auto &nx : p){cin >> nx;}
for(auto &nx : q){cin >> nx;}
vector<int> v(n);
for(int i=0;i<n;i++){v[p[i]-1]=q[i]-1;}
vector<vector<int>> dp(k+1,vector<int>(n+1,0));
dp[0][n]=1;
for(int i=0;i<n;i++){
vector<vector<int>> ndp(k+1,vector<int>(n+1,0));
for(int x=0;x<=k;x++){
for(int y=0;y<=n;y++){
if(x<k && v[i]<y){
ndp[x+1][y]+=dp[x][y];
ndp[x+1][y]%=mod;
}
ndp[x][min(y,v[i])]+=dp[x][y];
ndp[x][min(y,v[i])]%=mod;
}
}
dp.swap(ndp);
}
int res=0;
for(auto &nx : dp[k]){res+=nx;res%=mod;}
cout << res << '\n';
return 0;
}
posted:
last update: