题目链接:https://vjudge.net/problem/HDU-5677
题意:给n个字符串,问能不能找出K个这n个串形成的回文字串,使得总长度为L。
思路:
首先利用Manacher算法,得到长度为i的回文子串的个数num[i]。要注意bab中的包含bab、b、a、b四个回文串。
然后就是多重背包,用dp[i][j][k]表示长为1~i的回文串选择j个,总长度为k是否可行。因为每种长度的回文串有多个,就是多重背包,普通做法会超时,利用二进制优化(详见https://www.cnblogs.com/FrankChen831X/p/11423350.html),优化的实质就是把个数i转换成一系列二的次幂的数来代替原来的num[i]。优化之后大概有100*14=1400个回文串,用w1[i]表示其数量,w2[i]表示其长度(等于原长度 ×数量),然后要用滚动数组,不然dp数组的大小为1e7,每个case都memset,可能会超时。
总复杂度大概为O(K*L*L*logNL)。
AC code:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=105; int T,n,K,L,len,cnt,p[maxn<<1],num[maxn]; int w1[maxn*20],w2[maxn*20],dp[maxn][maxn]; char s[maxn<<1],ss[maxn]; void manacher(){ int mid=0,r=0; for(int i=1;i<len;++i){ if(r>=i) p[i]=min(p[(mid<<1)-i],r-i+1); while(s[i-p[i]]==s[i+p[i]]) ++p[i]; if(i+p[i]>r) r=i+p[i]-1,mid=i; ++num[p[i]-1]; } for(int i=1;i<=L;++i) num[i]+=num[i+2]; } int main(){ scanf("%d",&T); while(T--){ scanf("%d%d%d",&n,&K,&L); memset(dp,0,sizeof(dp)); cnt=0; for(int i=1;i<=n;++i){ scanf("%s",ss); len=strlen(ss); s[0]='~',s[1]='|'; for(int j=0;j<len;++j) s[2*j+2]=ss[j],s[2*j+3]='|'; len=2*len+2; for(int j=0;j<len;++j) p[j]=0; manacher(); } for(int i=1;i<=L;++i) if(num[i]){ for(int j=1;j<=num[i];j<<=1){ ++cnt; w1[cnt]=j; w2[cnt]=i*j; num[i]-=j; } if(num[i]){ ++cnt; w1[cnt]=num[i]; w2[cnt]=i*num[i]; num[i]=0; } } dp[0][0]=1; for(int i=1;i<=cnt;++i){ for(int j=K;j>=w1[i];--j) for(int k=L;k>=w2[i];--k) if(dp[j-w1[i]][k-w2[i]]) dp[j][k]=1; if(dp[K][L]) break; } if(dp[K][L]) printf("True "); else printf("False "); } return 0; }