hdoj5677(manacher+多重背包)

题目链接: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\n");
        else printf("False\n");
    }
    return 0;
}

 

posted @ 2020-03-04 23:05  Frank__Chen  阅读(149)  评论(0编辑  收藏  举报