[CF822E]Liar

xiaoxiao2021-02-28  90

题目大意

给定两个字符串S和T。你可以把S分成若干段,从左到右从1开始编号。现在要从你分出来的段中取出不超过x段,按编号从小到大依次拼接成字符串T。问是否有可行解。

1≤|T|≤|S|≤100000 x≤30

分析

考虑DP。设f[i][j]表示用了S的前i个字符,取出了j段,最大能得到T的前多少个字符。 转移分两种情况: 1. f[i][j]—>f[i+1][j] 表示字符i+1单独成段且没被选 2. f[i][j]—>f[i+lcp][j+1] 其中lcp是S的后缀i+1和T的后缀f[i][j]+1的lcp。这里表示这一段lcp被选中。很显然取lcp的长度比取小于lcp的长度要更优。

最终复杂度就取决于如何求lcp了。设字符串总长为N,如果用后缀数组加rmq可以做到O(NlogN)预处理+O(1)查询。不过时限较大,可以用hash+二分,时间复杂度为O(NxlogN)

#include <cstdio> #include <cstring> #include <algorithm> #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) using namespace std; const int N=100005,M=31,mo[2]={998244353,1004535809},INF=1e9; typedef long long LL; int n,m,K,h1[2][N],h2[2][N],p[2][N],f[N][M]; char s[N],t[N]; bool Same(int x,int y,int l) { for (int i=0;i<2;i++) { if ((mo[i]+(h1[i][x+l-1]-(LL)h1[i][x-1]*p[i][l])%mo[i])%mo[i]!=(mo[i]+(h2[i][y+l-1]-(LL)h2[i][y-1]*p[i][l])%mo[i])%mo[i]) return 0; } return 1; } int getlcp(int x,int y) { int l,r,mid; for (l=0,r=min(n-x+1,m-y+1),mid=r>>1;l<r;mid=l+r>>1) if (!Same(x,y,mid)) r=mid;else l=mid+1; if (!Same(x,y,l)) l--; return l; } int main() { scanf("%d%s%d%s%d",&n,s+1,&m,t+1,&K); for (int j=0;j<2;j++) { p[j][0]=1; for (int i=1;i<N;i++) p[j][i]=(LL)p[j][i-1]*26%mo[j]; for (int i=1;i<=n;i++) h1[j][i]=((LL)h1[j][i-1]*26+s[i]-'a')%mo[j]; for (int i=1;i<=m;i++) h2[j][i]=((LL)h2[j][i-1]*26+t[i]-'a')%mo[j]; } f[0][0]=0; for (int i=0;i<n;i++) { for (int j=0;j<K;j++) if (f[i][j]<m) { int k=f[i][j],lcp=getlcp(i+1,k+1); f[i+1][j]=max(f[i+1][j],k); if (lcp>0) f[i+lcp][j+1]=max(f[i+lcp][j+1],k+lcp); } } for (int i=1;i<=n;i++) for (int j=1;j<=K;j++) if (f[i][j]==m) { printf("YES\n"); return 0; } printf("NO\n"); return 0; }
转载请注明原文地址: https://www.6miu.com/read-50949.html

最新回复(0)