Official

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: