公式
E - 圧縮番号列の復元 / Restoration of Compressed Number Sequence 解説
by
E - 圧縮番号列の復元 / Restoration of Compressed Number Sequence 解説
by
physics0523
任意の箱の列に対する圧縮番号の割り当て方をそのまま DP に起こすことで、この問題を解くことができます。実際にやってみましょう。
圧縮番号は以下のように割り当てられます。
- 既に来たことのある番号が来た場合
- その番号に対応した圧縮番号が既に決まっており、それが使われます。
- まだ来たことのない番号が来た場合
- 新たな圧縮番号が今来た番号に対して払い出されます。
これを DP に起こします。
\(dp[\) 直前に払い出された圧縮番号 \(]=\{\) 場合の数 \(\}\) を実行しましょう。
\(A_i\) を考慮する前の DP テーブルを \(dp\) 、考慮した後の DP テーブルを \(ndp\) とします。
\(i\) 項目の \(dp[j]\) からの遷移は次の通りです。
- \(A_i = 0\) の場合 (圧縮番号が判読不可である場合)
- 既存の圧縮番号が使われるケースとして、 \(ndp[j]\) に \(dp[j] \times j\) を加算する。(使う圧縮番号だけ自由度があります)
- 新たな圧縮番号が払い出されるケースとして、 \(ndp[j+1]\) に \(dp[j] \times (M-j)\) を加算する。(割り当てる元の番号だけ自由度があります)
- \(A_i \neq 0\) の場合 (圧縮番号が決まっている場合)
- \(A_i \le j\) の場合、既存の圧縮番号を使うことになるので \(ndp[j]\) に \(dp[j]\) を加算する。
- \(A_i = j+1\) の場合、新たな圧縮番号を払い出す必要がある ( = 今までに来たことのない番号が来る) ので \(ndp[j+1]\) に \(dp[j] \times (M-j)\) を加算する。(割り当てる元の番号だけ自由度があります)
- どちらでもない場合、圧縮番号 \(A_i\) を与えることが不可能なのでそこからは遷移しない。
DP テーブルの大きさは \(O(\min(N,M))\) でよいので、時間計算量が \(O(N \times \min(N,M))\) の解法を得ることができました。
実装例 (C++):
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=998244353;
int main(){
ll N,M;
cin >> N >> M;
ll dmx=min(N,M);
vector<ll> dp(dmx+1,0);
dp[0]=1;
for(ll i=0;i<N;i++){
ll a;
cin >> a;
vector<ll> ndp(dmx+1,0);
for(ll j=0;j<=dmx;j++){
if(a==0){
ndp[j]+=dp[j]*j;
ndp[j]%=mod;
if(j!=dmx){
ndp[j+1]+=dp[j]*(M-j);
ndp[j+1]%=mod;
}
}
else if(a<=j){
ndp[j]+=dp[j];
ndp[j]%=mod;
}
else if(a==j+1){
if(j!=dmx){
ndp[j+1]+=dp[j]*(M-j);
ndp[j+1]%=mod;
}
}
else{}
}
dp=ndp;
}
ll res=0;
for(auto &nx : dp){res+=nx;}
cout << res%mod << "\n";
return 0;
}
投稿日時:
最終更新:
