• 【XSY3896】相似(dp套dp,状压)


    题面

    相似

    题解

    可以发现,(S)(T) 相似,等价于它们的最长公共子序列长度至少为 (n - k)

    首先考虑如何求出两个字符串的 ( ext{LCS})(最长公共子序列)。考虑 dp:

    (f_{i,j}) 表示 (S[1sim i])(T[1sim j])( ext{LCS}) 长度,转移是显然的:

    [egin{aligned} f_{i,j}= egin{cases} f_{i-1,j-1}+1& operatorname{if }S[i]=T[j]\ max(f_{i-1,j},f_{i,j-1})& operatorname{if }S[i] eq T[j] end{cases} end{aligned} ]

    然后考虑如何拓展到 (T) 任意的情况。自然的想法是 dp 套 dp:

    (dp[j][f_{1,j},f_{2,j},cdots,f_{n,j}]) 表示只考虑 (T) 的前 (j) 位,那么这 (j) 位有多少种选取方式,使此时的 (f_{*,j}) 数组如 dp 下标所示。

    这个状态设计显然是可以优化的,不难证得 (f_{i+1,j}-f_{i,j}in{0,1}),又由于 (f_{0,j}=0),所以我们把 (dp) 数组的第二维变成差分数组 (c_i=f_{i+1}-f_{i,j}) 的 01 串状压即可。

    考虑这个 dp 的时间复杂度,状态数是 (O(n2^n)) 的。直接暴力 DP 的时间复杂度就约为 (O(2^noperatorname{poly}(n)))。考虑如何利用 (k) 很小这个性质进行优化。

    首先考虑优化状态数。

    考虑某个满足 (|i-j|>k)(f_{i,j})(显然有 (f_{i,j}leq min(i,j))),再考虑这个 (f_{i,j}) 转移到 (f_{n,n}) 时最大会变成多少:观察最上面给出的 (f_{i,j}) 递推式,显然按第一种情况转移值才会变大,而第一种情况最多转移 (min(n-i,n-j)) 次,所以从 (f_{i,j}) 转移到 (f_{n,n}) 时的值不大于:

    [f_{i,j}+min(n-i,n-j)leq min(i,j)+min(n-i,n-j)=n-(max(i,j)-min(i,j))<n-k ]

    所以这个 (f_{i,j}) 不可能使 (f_{n,n}geq n-k)

    于是,与其记录整个 (f) 数组,我们只需要记录 (f_{j−k,j} , f_{j−k+1,j},cdots, f_{j+k,j}),这可以通过一个长度为 (2k) 的差分数组以及一个 (f_{j,j}) 得到。(这个差分数组也可以用 01 串状压表示)

    又由于 (f_{j,j}) 的值必须介于 ([j − k, j]) 之间才能使 (f_{n,n}geq n-k),所以我们将 (dp) 数组的第二维换成一个数 (xin [0,k]) 表示 (f_{j,j}=j-k+x),第三维换成差分数组的 01 串状压。此时 (dp) 的总状态数只有 (O(nk2^{2k}))

    再考虑优化转移。

    转移的过程是这样的:对于 (dp[j][x][sta]),枚举一个字符 (c)(T[j+1]),向 (dp[j+1][x'][sta']) 转移。(其中 (x')(sta') 需要我们计算出来)

    为了方便转移,我们可以先预处理一个 pair 数组 (turn[j][x][sta][match]),表示当 (dp[j][x][sta])(dp[j+1][x'][sta']) 转移,枚举的字符 (c)(S[j-k+1],S[j-k+2],cdots,S[j+k+1]) 的匹配状态为 (match) 时,(x')(sta') 分别是多少。

    也就是说,我们知道了 (f_{j-ksim j+k,j})(match),要求此时的 (f_{j-k+1sim j+k+1,j+1})

    按照最上面的递推式正常转移即可。

    然后你会发现一个很神奇的东西:要求出 (x)(sta) 的变化量根本不需要 (j),只需要 (x)(sta)

    所以 (turn) 的第一维 (j) 可以直接去掉。

    所以才叫预处理 turn。

    注意 (match) 也是可以预处理的,然后此时预处理的时间是 (O(k^22^{2k}2^{2k+1}+26nk)=O(k^22^{4k+1}+26nk)),可以接受。

    转移的时间优化到了 (O(26nk2^{2k}))

    其实已经能跑过了,但有点卡。

    所以可以进一步的优化:

    你发现对于枚举的 (c),只要 (c) 不在 (S[j-k+1],S[j-k+2],cdots,S[j+k+1]) 内出现过,(match) 就等于 (0),也就是说它们都相等,此时得到的 (x')(sta') 都是相同的。

    所以说,假设 (S[j-k+1],S[j-k+2],cdots,S[j+k+1]) 中有 (num) 种字符,那么我们一次算出 (match=0) 时的 (x')(sta'),然后转移 (dp[j+1][x'][sta']gets dp[j+1][x'][sta']+(26-num)dp[j][x][sta]) 即可。

    易知 (numleq 2k+2leq 9),所以这部分我们枚举 (c)(turn) 数组转移即可。

    转移的时间复杂度降低到了约 (O(10nk2^{2k}))

    然后能 1s AC 了。(时限 2s)

    #include<bits/stdc++.h>
    
    #define K 5
    #define N 30010
    #define mod 998244353
    
    using namespace std;
    
    inline int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<1)+(x<<3)+(ch^'0');
    		ch=getchar();
    	}
    	return x*f;
    }
    
    namespace modular
    {
    	inline int add(int x,int y){if((x+=y)>=mod)x-=mod; return x;}
    	inline int dec(int x,int y){if((x-=y)<0)x+=mod; return x;}
    	inline int mul(int x,int y){return 1ll*x*y%mod;}
    }
    
    using namespace modular;
    
    struct data
    {
    	int x,sta;
    }turn[K][260][520];
    
    int n,k,s[N];
    int match[N][26];
    int dp[N][K][260];
    int f[K<<1],g[K<<1];
    char ss[N];
    bool vis[30];
    
    int main(){
    	scanf("%s%d",ss+1,&k);
    	n=strlen(ss+1);
    	for(int i=1;i<=n;i++) s[i]=ss[i]-'A';
    	int pow2k=(1<<(k<<1)),pow2k1=pow2k<<1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int c=0;c<26;c++)
    		{
    			int sta=0;
    			for(int l=i+k;l>=i-k;l--)
    			{
    				if(l<1||l>n) sta<<=1;
    				else sta=sta<<1|(c==s[l]);
    			}
    			match[i][c]=sta;
    		}
    	}
    	for(int x=0;x<=k;x++)
    	{
    		for(int sta=0;sta<pow2k;sta++)
    		{
    			f[k]=x;
    			for(int i=k+1;i<=2*k;i++) f[i]=f[i-1]+((sta>>(i-1))&1);
    			for(int i=k-1;i>=0;i--) f[i]=f[i+1]-((sta>>i)&1);
    			for(int mat=0;mat<pow2k1;mat++)
    			{
    				for(int i=1;i<=2*k+1;i++)
    				{
    					if((mat>>(i-1))&1) g[i]=f[i-1]+1;
    					else g[i]=max(g[i-1],f[i]);
    				}
    				turn[x][sta][mat].x=g[k+1]-1;
    				int nsta=0;
    				for(int i=2*k;i>=1;i--) nsta=nsta<<1|(g[i+1]-g[i]);
    				turn[x][sta][mat].sta=nsta;
    			}
    		}
    	}
    	dp[0][k][0]=1;
    	for(int i=0;i<n;i++)
    	{
    		for(int x=0;x<=k;x++)
    		{
    			for(int sta=0;sta<pow2k;sta++)
    			{
    				if(!dp[i][x][sta]) continue;
    				int num=0;
    				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
    				{
    					if(!vis[s[j]]) num++;
    					vis[s[j]]=1;
    				}
    				int nx=turn[x][sta][0].x,nsta=turn[x][sta][0].sta;
    				if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],mul(26-num,dp[i][x][sta]));
    				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
    				{
    					if(!vis[s[j]]) continue;
    					vis[s[j]]=0;
    					nx=turn[x][sta][match[i+1][s[j]]].x,nsta=turn[x][sta][match[i+1][s[j]]].sta;
    					if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],dp[i][x][sta]);
    				}
    			}
    		}
    	}
    	int ans=0;
    	for(int x=0;x<=k;x++)
    		for(int sta=0;sta<pow2k;sta++)
    			ans=add(ans,dp[n][x][sta]);
    	printf("%d
    ",ans);
    	return 0;
    }
    /*
    GUGUA
    1
    */
    
  • 相关阅读:
    sql 笔记之一
    js的Location
    VS无法使用.XXXX附加到程序
    C#遍历对象的方法
    解决tomcat启动时中文乱码问题。
    动态sql foreach 循环报错问题
    java跳过https证书直接请求工具类
    安装svn报2503错误处理方法
    oracle常用的一些查询命令
    Oracle11g安装过程中忘记进行口令配置
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14508506.html
Copyright © 2020-2023  润新知