给两个串,a与b,长度1e5,最多可以在a中选出x段互不相交的连续子串,问能不能拼成b,x的范围最多为30。
解法:
可以想到dp,dp[i][j]表示 a 串前 j 个字符取 i 段,能匹配到 b 串的最大前缀。这个状态是通过贪心构造的。
转移的时候也是贪心转移,分为多一段和不要这个字符两种情况。
a) 多一段:dp[i+1][j+lcp(a[j],b[dp[i][j]])]=max(dp[i+1][j+lcp(a[j],b[dp[i][j]])],dp[i][j]+lcp(a[j],dp[i][j])); 因为如果要从a串的a[j]处, b串的b[dp[i][j]]处取一段相同子串,可以贪心地取最长的一段,其中lcp(a[j],dp[i][j]) 可以拿后缀数组或者哈希维护。
b) 不要当前字符:dp[i][j+1]=max(dp[i][j+1],dp[i][j]);
#include <bits/stdc++.h> using namespace std; const int maxn=200005; string s,t,o; bool flag; int N,K,Rank[maxn],tmp[maxn],sa[maxn],lcp[maxn],n,m,x,st[20][maxn],dp[32][maxn]; inline bool compare_sa(int i,int j) { if (Rank[i]!=Rank[j]) return Rank[i]<Rank[j]; else { int ri=(i+K<=N)?Rank[i+K]:-1; int rj=(j+K<=N)?Rank[j+K]:-1; return ri<rj; } } inline void construct_sa(string S,int *sa) { N=S.length(); for (int i=0;i<=N;++i) { sa[i]=i; Rank[i]=i<N?S[i]:-1; } for (K=1;K<=N;K*=2) { sort(sa,sa+N+1,compare_sa); tmp[sa[0]]=0; for (int i=1;i<=N;++i) tmp[sa[i]]=tmp[sa[i-1]]+(compare_sa(sa[i-1],sa[i])?1:0); for (int i=0;i<=N;++i) Rank[i]=tmp[i]; } } inline void construct_lcp(string S,int *sa,int *lcp) { N=S.length(); int h=0; lcp[0]=0; for (int i=0;i<N;++i) { int j=sa[Rank[i]-1]; h=max(h-1,0); for (;j+h<N&&i+h<N;++h) if (S[j+h]!=S[i+h]) break; lcp[Rank[i]-1]=h; } } inline void construct_rmq(int *lcp,int n) { for (int i=0;i<n;++i) st[0][i]=lcp[i]; for (int i=1;i<20;++i) for (int j=0;j+(1<<i)-1<n;++j) st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]); } inline int query_rmq(int i,int j) { int x=0; while ((1<<(x+1))<=j-i+1) ++x; return min(st[x][i],st[x][j-(1<<x)+1]); } void solve() { for (int i=0;i<=x;++i) for (int j=0;j<=n;++j) { if (dp[i][j]>=m) { puts("YES"); return; } int _lcp=query_rmq(min(Rank[j],Rank[dp[i][j]+n+1]),max(Rank[j],Rank[dp[i][j]+n+1])-1); dp[i+1][j+_lcp]=max(dp[i+1][j+_lcp],dp[i][j]+_lcp); dp[i][j+1]=max(dp[i][j+1],dp[i][j]); } puts("NO"); } int main() { cin>>n>>s>>m>>t>>x; o=s+'$'+t; construct_sa(o,sa); construct_lcp(o,sa,lcp); construct_rmq(lcp,o.length()+1); solve(); return 0; }