题解:P4590 [TJOI2018] 游园会
forest114514 · · 题解
DP 套 DP 的板子题,但我们可以在大家都会的做法上再优化一点。
大家都知道 LCS 的 DP 方程:
然后观察性质发现同一行
但是我们知道 DP 套 DP 的状态数不要脑测,比如麻将和移除石子的状态数看着是指数级别的但是搜出来只有几千,这题是类似的。
我们借鉴之前解法压缩状态的做法然后直接 dfs 一下搜索合法状态,发现随机输入几个长
放一下搜状态的代码:
int dfs(int sta){
if(vis[sta]) return vis[sta];
vis[sta]=++tot;
auto work=[&](int to,char c)->void{
rep(i,1,k) g[0][i]=g[0][i-1]+((sta>>(i-1))&1);
len[vis[sta]]=g[0][k];
int nxt=0;
rep(i,1,k){
g[1][i]=max(g[0][i],g[1][i-1]);
if(s[i]==c) g[1][i]=max(g[1][i],g[0][i-1]+1);
nxt|=((g[1][i]-g[1][i-1])<<(i-1));
}
trans[vis[sta]][to]=dfs(nxt);
};
work(0,'N');
work(1,'O');
work(2,'I');
return vis[sta];
}
这样我们就做到了
这样跑得飞快,加了取模优化后最慢点 35ms,成功拿下最优解(2024.7.16)。
代码:
const int N=1e3+100,M=6005+100,mod=1e9+7;
int n,k,f[2][M][3],g[2][20],trans[M][3],vis[1<<15],len[M],tot;
string s;
int dfs(int sta){//搜状态
if(vis[sta]) return vis[sta];
vis[sta]=++tot;
auto work=[&](int to,char c)->void{
rep(i,1,k) g[0][i]=g[0][i-1]+((sta>>(i-1))&1);
len[vis[sta]]=g[0][k];
int nxt=0;
rep(i,1,k){
g[1][i]=max(g[0][i],g[1][i-1]);
if(s[i]==c) g[1][i]=max(g[1][i],g[0][i-1]+1);
nxt|=((g[1][i]-g[1][i-1])<<(i-1));
}
trans[vis[sta]][to]=dfs(nxt);
};
work(0,'N');
work(1,'O');
work(2,'I');
return vis[sta];
}
void _add(int &u,int v){
u=(u+v>=mod)?u+v-mod:u+v;
}
int ans[25];
signed main(){
read(n,k);
cin>>s;s=' '+s;
dfs(0);
f[0][vis[0]][0]=1;
rep(i,0,n-1){
int o=i&1;
rep(j,1,tot){//枚举状态
if(f[o][j][0]){
_add(f[o^1][trans[j][0]][1],f[o][j][0]);//加一个 N
_add(f[o^1][trans[j][1]][0],f[o][j][0]);//加一个 O
_add(f[o^1][trans[j][2]][0],f[o][j][0]);//加一个 I
}
if(f[o][j][1]){
_add(f[o^1][trans[j][0]][1],f[o][j][1]);//加一个 N
_add(f[o^1][trans[j][1]][2],f[o][j][1]);//加一个 O
_add(f[o^1][trans[j][2]][0],f[o][j][1]);//加一个 I
}
if(f[o][j][2]){
_add(f[o^1][trans[j][0]][1],f[o][j][2]);//加一个 N
_add(f[o^1][trans[j][1]][0],f[o][j][2]);//加一个 O
//不能加 I,不然就变成 NO+I 出现 NOI 了
}
f[o][j][0]=f[o][j][1]=f[o][j][2]=0;
}
}
rep(j,1,tot) rep(k,0,2) _add(ans[len[j]],f[n&1][j][k]);
rep(i,0,k) write(ans[i],'\n');
return 0;
}