[XSY] 相似(DP套DP)

相似

在看这道题前,有必要先看一下DP套DP的入门题[uoj3864]Hero meet devil,附上两篇写得不错的题解:
https://blog.csdn.net/Ike940067893/article/details/87863041
https://www.cnblogs.com/RabbitHu/p/BZOJ3864.html

  • 可以发现, S S S T T T相似,等价于它们的最长公共子序列长度至少为 n − k n-k nk
  • 考虑如何求两个字符串的LCS。设 l c s i , j lcs_{i,j} lcsi,j 表示 S [ 1... i ] S[1...i] S[1...i] T [ 1... j ] T[1...j] T[1...j] 的 LCS 长度,转移显然为
    l c s i , j = { l c s i − 1 , j − 1 + 1 ( S [ i ] = = T [ j ] ) m a x { l c s i − 1 , j , l c s i , j − 1 } ( S [ i ] ! = T [ j ] ) lcs_{i,j}=\left\{ \begin{aligned} &lcs_{i-1,j-1}+1(S[i]==T[j]) \\ & max\{lcs_{i-1,j},lcs_{i,j-1}\} (S[i]!=T[j])\\ \end{aligned} \right. lcsi,j={ lcsi1,j1+1(S[i]==T[j])max{ lcsi1,j,lcsi,j1}(S[i]!=T[j])
  • 考虑如何拓展到 T T T任意的情况。自然的想法是DP套DP:设 d p [ j ] [ l c s 1 , j , l c s 2 , j , . . . , l c s n , j ] dp[j][lcs_{1,j},lcs_{2,j},...,lcs_{n,j}] dp[j][lcs1,j,lcs2,j,...,lcsn,j]表示 考虑到 T T T的前 j j j位, l c s − , j lcs_{-,j} lcs,j的情况如第二维所示 的方案数
  • 直接这样维护状态肯定是行不通的,考虑优化:
    1.发现 l c s i , j − l c s i − 1 , j ∈ { 0 , 1 } lcs_{i,j}-lcs_{i-1,j}\in\{0,1\} lcsi,jlcsi1,j{ 0,1},那么 l c s 1 , j , l c s 2 , j , . . . , l c s n , j lcs_{1,j},lcs_{2,j},...,lcs_{n,j} lcs1,j,lcs2,j,...,lcsn,j的差分数组必是一个01 串 ,我们 状压这个01串 来描述第二维的状态
    2. n n n位01串还是太大了,考虑如何利用 k k k很小这个性质进行优化
    考 虑 某 个 l c s i , j , 若 ∣ i − j ∣ > k , 那 么 在 转 移 到 l c s n , n 时 , 必 然 值 不 超 过 l c s i , j + m i n { n − i , n − j } < = m i n { i , j } + m i n { n − i , n − j } = n − ∣ i − j ∣ < n − k \color{Red}{考虑某个lcs_{i,j}},若|i-j|>k,那么在转移到lcs_{n,n}时,必然值不超过lcs_{i,j}+min\{n-i,n-j\}<=min\{i,j\}+min\{n-i,n-j\}=n-|i-j|<n-k lcsi,jij>klcsn,nlcsi,j+min{ ni,nj}<=min{ i,j}+min{ ni,nj}=nij<nk
    因此这样的 l c s i , j lcs_{i,j} lcsi,j是没用的,可以直接丢掉,即对于每个 j j j,我们只需要记录 l c s j − k , j , l c s j − k + 1 , j , . . . , l c s j + k , j lcs_{j-k,j},lcs_{j-k+1,j},...,lcs_{j+k,j} lcsjk,j,lcsjk+1,j,...,lcsj+k,j
    3.只知道 l c s j − k , j lcs_{j-k,j} lcsjk,j~ l c s j + k , j lcs_{j+k,j} lcsj+k,j的差分数组,我们是无法知道它们的实际值的,因此考虑开多一维来记录 l c s j , j lcs_{j,j} lcsj,j的实际值以推出其它的 l c s lcs lcs值,即我们设 d p [ j ] [ x ] [ s ] dp[j][x][s] dp[j][x][s]表示 考虑到 T T T的前 j j j位, l c s j , j = = x lcs_{j,j}==x lcsj,j==x l c s j − k , j , l c s j − k + 1 , j , . . . , l c s j + k , j lcs_{j-k,j},lcs_{j-k+1,j},...,lcs_{j+k,j} lcsjk,j,lcsjk+1,j,...,lcsj+k,j的差分数组状压后为 s s s 的方案数
    4. ∵ l c s n , n ∈ [ n − k , n ] ( 即 不 匹 配 的 字 符 最 多 k 个 ) \because lcs_{n,n}\in [n-k,n](即不匹配的字符最多k个) lcsn,n[nk,n](k)
    ∴ ∀ j ∈ [ 1 , n ] 满 足 l c s j , j ∈ [ j − k , j ] \therefore\forall j\in[1,n]满足lcs_{j,j}\in [j-k,j] j[1,n]lcsj,j[jk,j],所以我们可以将 d p dp dp的第二维设置为 l c s j , j − ( j − k ) = l c s j , j − j + k lcs_{j,j}-(j-k)=lcs_{j,j}-j+k lcsj,j(jk)=lcsj,jj+k,即 d p [ ] [ x ] [ ] dp[][x][] dp[][x][]表示 l c s j , j − j + k = = x lcs_{j,j}-j+k==x lcsj,jj+k==x,这样就令 x ∈ [ 0 , k ] x\in[0,k] x[0,k]
  • 考虑如何实现状态转移。 d p dp dp数组的第三维不好直接得到,我们考虑把它预处理出来:设 p [ i ] [ j ] [ s 1 ] [ c ] p[i][j][s1][c] p[i][j][s1][c]表示考虑到 T T T的前 i i i位, l c s i , i = = j lcs_{i,i}==j lcsi,i==j(知道 l c s i , i lcs_{i,i} lcsi,i就可以通过差分数组推出其它的 l c s lcs lcs值), l c s i − k , i , l c s i − k + 1 , i , . . . , l c s i + k , i lcs_{i-k,i},lcs_{i-k+1,i},...,lcs_{i+k,i} lcsik,i,lcsik+1,i,...,lcsi+k,i的差分数组状压后为s1,在 T T T i + 1 i+1 i+1位新增字符 c c c后, l c s i + 1 − k , i + 1 , l c s i + 2 − k , i + 1 , . . . , l c s i + 1 + k , i + 1 lcs_{i+1-k,i+1},lcs_{i+2-k,i+1},...,lcs_{i+1+k,i+1} lcsi+1k,i+1,lcsi+2k,i+1,...,lcsi+1+k,i+1状压完的差分数组
  • 同样,需要优化 p p p的状态设置:
    1.同上,我们可以将 p p p的第二维设置为 l c s i , i − ( i − k ) = l c s i , i − i + k lcs_{i,i}-(i-k)=lcs_{i,i}-i+k lcsi,i(ik)=lcsi,ii+k,即 p [ ] [ j ] [ ] [ ] p[][j][][] p[][j][][]表示 l c s i , i − i + k = = j lcs_{i,i}-i+k==j lcsi,ii+k==j,这样就令 j ∈ [ 0 , k ] j\in[0,k] j[0,k]
    2.其实 p p p转移到的新差分数组状态与新增字符具体是什么无关,与新增字符与S每一位的匹配情况有关。再进一步地,
    ∵ l c s h , h ∈ [ h − k , h ] \because lcs_{h,h}\in [h-k,h] lcsh,h[hk,h]
    ∴ T [ h ] 只 可 能 与 S [ h − k ] \therefore T[h]只可能与 S[h-k] T[h]S[hk]~ S [ h + k ] 匹 配 S[h+k] 匹配 S[h+k],即我们只需要考虑新增字符与 S [ i + 1 − k ] S[i+1-k] S[i+1k]~ S [ i + 1 + k ] S[i+1+k] S[i+1+k]的匹配情况。直接把第四维设置成这个匹配情况即可。
    3.仔细想想,把第四维改成匹配情况后, p p p的转移已经与第一维无关了,直接删掉即可

至此,这道题就可以实现了

#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 998244353
using namespace std;
typedef pair<int,int> pr;
const int N=30001;
const int K=5;
const int S=256;
int n,m,ans,dp[N][K][S],f[31],g[31];
pr p[K][S][S<<1];
char ch[N];
bool flag[31];
int add(int a,int b){
    
    
	a=a+b;
	if(a>mod) a-=mod;
	return a;
} 
int mul(int a,int b){
    
    
	return 1ll*a*b%mod;
}
void dfs(int a,int b,int c){
    
    
	if(dp[a][b][c]==0) return;
	int l=max(a-m,0),r=min(a+m,n-1),sz=26;
	for(int i=l;i<=r;i++){
    
    
		if(!flag[ch[i+1]-'A']){
    
    
			flag[ch[i+1]-'A']=1;
			sz--;
		}
		f[ch[i+1]-'A']|=(1<<(i-a+m));//统计每种字符的匹配情况 
	}
	for(int i=l;i<=r;i++){
    
    
        if(flag[ch[i+1]-'A']){
    
    
            int x=p[b][c][f[ch[i+1]-'A']].first;
			int y=p[b][c][f[ch[i+1]-'A']].second;
            if(x>=0) dp[a+1][x][y]=add(dp[a+1][x][y],dp[a][b][c]);
            flag[ch[i+1]-'A']=0;
            f[ch[i+1]-'A']=0;
        }
    }
    int x=p[b][c][0].first,y=p[b][c][0].second;
    if(x>=0) dp[a+1][x][y]=add(dp[a+1][x][y],mul(dp[a][b][c],sz));
}
int main(){
    
    
	scanf("%s%d",ch+1,&m);
	n=strlen(ch+1);
	//省略一维l,表示考虑到第l位 
	for(int i=0;i<=m;i++){
    
    //lcs[l][l]-l+m==i 
		for(int j=0;j<(1<<(2*m));j++){
    
    //lcs[l-m][l]~lcs[l+m][l]的差分数组状压 
			for(int k=0;k<(1<<(2*m+1));k++){
    
    //T[l+1]与S[l+1-m]~S[l+1+m]匹配情况状压 
				//f:(lcs[l-m][l]-l+m)~(lcs[l+m][l]-l+m) 
				//g:(lcs[l+1-m][l+1]-l+m)~(lcs[l+1+m][l+1]-l+m)
				f[m]=i;
				for(int a=m-1;a>=0;a--) f[a]=f[a+1]-(j>>a&1);
				for(int a=m;a<2*m;a++) f[a+1]=f[a]+(j>>a&1);
				for(int a=0;a<=2*m;a++){
    
    
                    if((k>>a)&1) g[a+1]=max(f[a]+1,f[a+1]);//匹配
                    else g[a+1]=max(f[a],f[a+1]); //不匹配 
                    g[a+1]=max(g[a],g[a+1]);
                }
                int x=g[m+1]-1;//x=lcs[l+1][l+1]-(l+1)+m
                int y=0;
                if(x>=0){
    
    
                    for(int a=m-1;a>=0;a--) 
						if(g[a+1]^g[a+2]) y|=1<<a;
                    for(int a=m;a<2*m;a++) 
						if(g[a+1]^g[a+2]) y|=1<<a;
                }
                p[i][j][k]=make_pair(x,y);
			}
		}
	}//预处理 
	for(int i=0;i<=30;i++) f[i]=0;
	dp[0][m][0]=1;
	for(int i=0;i<n;i++){
    
    //考虑到第i位 
        for(int j=0;j<=m;j++){
    
    //lcs[i][i]-i+m==j 
            for(int k=0;k<(1<<(2*m));k++){
    
    //差分数组状压 
                dfs(i,j,k);
            }
        }
    }//dp
    for(int i=0;i<=m;i++){
    
    
        for(int j=0;j<(1<<(2*m));j++){
    
    
            ans=add(ans,dp[n][i][j]);
        }
    }
    printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Emma2oo6/article/details/114375304