2 条题解

  • 0
    @ 2026-7-4 11:24:08

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 20;
    const int N = 32780;
    const int MOD = 1e9+7;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M],b[M],ans[M],siz[N],dp[2][N][3];char s[M];
    int encode(int *a)
    {
    	int r=0;
    	for(int i=0;i<m;i++)
    		r|=(a[i+1]-a[i])<<i;
    	return r;
    }
    void decode(int *a,int r)
    {
    	for(int i=0;i<m;i++) a[i+1]=(r>>i)&1;
    	for(int i=1;i<=m;i++) a[i]+=a[i-1];
    }
    void trans(int w,int r,int p,char c,int v)
    {
    	decode(a,r);
    	for(int i=1;i<=m;i++)
    		b[i]=max(max(a[i],b[i-1]),a[i-1]+(c==s[i]));
    	int tr=encode(b);
    	dp[w][tr][p]=(dp[w][tr][p]+v)%MOD;
    }
    signed main()
    {
    	n=read();m=read();
    	scanf("%s",s+1);dp[0][0][0]=1;
    	for(int i=1;i<(1<<15);i++) siz[i]=siz[i>>1]+(i&1);
    	for(int i=0;i<n;i++)
    	{
    		int w=(i&1),tw=w^1;
    		for(int j=0;j<(1<<m);j++)
    			for(int p=0;p<3;p++)
    				dp[tw][j][p]=0;
    		for(int j=0;j<(1<<m);j++)
    		{
    			if(dp[w][j][0])
    			{
    				trans(tw,j,1,'N',dp[w][j][0]);
    				trans(tw,j,0,'O',dp[w][j][0]);
    				trans(tw,j,0,'I',dp[w][j][0]);
    			}
    			if(dp[w][j][1])
    			{
    				trans(tw,j,1,'N',dp[w][j][1]);
    				trans(tw,j,2,'O',dp[w][j][1]);
    				trans(tw,j,0,'I',dp[w][j][1]);
    			}
    			if(dp[w][j][2])
    			{
    				trans(tw,j,1,'N',dp[w][j][2]);
    				trans(tw,j,0,'O',dp[w][j][2]);
    			}
    		}
    	}
    	for(int i=0;i<(1<<m);i++)
    		for(int p=0;p<3;p++)
    			ans[siz[i]]=(ans[siz[i]]+dp[n&1][i][p])%MOD;
    	for(int i=0;i<=m;i++)
    		printf("%d\n",ans[i]);
    }
    
    
    • 0
      @ 2026-5-8 23:42:19

      DP 套 DP 的板子题,但我们可以在大家都会的做法上再优化一点。

      大家都知道 LCS 的 DP 方程:fi,jf_{i,j} 表示奖章串前 ii 位和兑奖串前 jj 位的 LCS,转移大家都会就不讲了。

      然后观察性质发现同一行 fif_{i} 满足 fi,jfi,j1{0,1}f_{i,j}-f_{i,j-1}\in \{0,1\}。我们就能直接把一维压成一个二进制数然后 DP 就行了,这样预处理转移可以做到 O(n2k)O(n2^k),这个其他题解都提过,DP 套 DP 怎么转移其实就是一个简单的自动机上 DP,枚举状态和转移边即可,我们就不细讲了,可以看我的代码。

      但是我们知道 DP 套 DP 的状态数不要脑测,比如麻将移除石子的状态数看着是指数级别的但是搜出来只有几千,这题是类似的。

      我们借鉴之前解法压缩状态的做法然后直接 dfs 一下搜索合法状态,发现随机输入几个长 1515 的串只有 100020001000\sim 2000 的状态数,实际测下来状态数不超过 60006000,而且随机串数据下很难卡满,比直接状压 2k2^{k} 的数组状态数要小太多了。

      放一下搜状态的代码:

      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];
      }
      

      这样我们就做到了 O(nΣ)O( n|\Sigma|) 的复杂度,其中 Σ|\Sigma| 为搜出来 LCS 数组的状态数,当然有一个 99 倍的常数。

      这样跑得飞快,加了取模优化后最慢点 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;
      }
      
      • 1

      信息

      ID
      10507
      时间
      6000ms
      内存
      256MiB
      难度
      10
      标签
      递交数
      2
      已通过
      1
      上传者