「SWTR-05」String
阿新 • • 發佈:2021-10-12
Description
給定串 \(S\) 和 \(T\),每次可以在 \(T\) 前或後拼接一個 \(T\) 的子串,求最小步數使得 \(T\) 變成 \(S\)。
Solution
暴力區間dp,剪剪枝有可以做到 50%,考場上沒判 -1,怒丟 40pts。
考慮一個區間 \([l,r]\) 能有的轉移,假設其能轉移到 \([l',r]\),其中 \(l'\in [x,l)\),那麼實際上我們只需要保留 \([x,l]\) 這個區間進行轉移,也就是每次找最遠的能轉移的區間進行轉移。考慮為什麼是對的。大區間一定是包含小區間的所有子串的,而選擇大區間還會使得剩下的串更小,不會使得答案更劣。那麼現在的問題就是快速處理出一個區間最多能擴充套件到哪兒。類比於上面的道理,記一個區間 \([l,r]\)
計算答案可以用 bfs,因為每次轉移增量都是一。
#include<stdio.h> #include<string.h> #include<queue> using namespace std; typedef long long ll; const int Bs=233; const int N=5e3+7; const int Mod=998244353; struct Seg{ int l,r; Seg(int l_=0,int r_=0):l(l_),r(r_){} }; queue<Seg> Q; char s[N],t[N]; ll pw[N],hs=0,Hs[N]; int toL[N][N],toR[N][N],dp[N][N]; inline ll Get(int l,int r){ return (Hs[r]-Hs[l-1]*pw[r-l+1]%Mod+Mod)%Mod; } int main(){ // freopen("cutstring.in","r",stdin); // freopen("cutstring.out","w",stdout); scanf("%s%s",s+1,t+1); int n=strlen(s+1),m=strlen(t+1); pw[0]=1; for(int i=1;i<=n;i++) pw[i]=pw[i-1]*Bs%Mod, Hs[i]=(Hs[i-1]*Bs%Mod+s[i])%Mod; for(int i=1;i<=m;i++) hs=(hs*Bs%Mod+t[i])%Mod; for(int l=1;l<=n;l++){ int ret=1; for(int r=l;r<=n;r++){ while((l-ret>=1&&r-l+1>=ret)&&Get(r-ret+1,r)==Get(l-ret,l-1)) ret++; toL[l][r]=ret-1; } } for(int r=n;r;r--){ int ret=1; for(int l=r;l;l--){ while((r+ret<=n&&r-l+1>=ret)&&Get(l,l+ret-1)==Get(r+1,r+ret)) ret++; toR[l][r]=ret-1; } } // for(int i=1;i<=n;i++) // for(int j=i;j<=n;j++) // printf("%d %d %d %d\n",i,j,toL[i][j],toR[i][j]); memset(dp,-1,sizeof(dp)); for(int i=1;i<=n;i++) if(Get(i,i+m-1)==hs) Q.push(Seg(i,i+m-1)),dp[i][i+m-1]=0; while(!Q.empty()){ Seg t=Q.front(); Q.pop(); // printf("%d %d %d\n",t.l,t.r,dp[t.l][t.r]); if(toL[t.l][t.r]){ const int del=toL[t.l][t.r]; if(dp[t.l-del][t.r]==-1){ dp[t.l-del][t.r]=dp[t.l][t.r]+1; Q.push(Seg(t.l-del,t.r)); } } if(toR[t.l][t.r]){ const int del=toR[t.l][t.r]; if(dp[t.l][t.r+del]==-1){ dp[t.l][t.r+del]=dp[t.l][t.r]+1; Q.push(Seg(t.l,t.r+del)); } } } printf("%d",dp[1][n]); }