题目链接:HDU 5677 ztr loves substring
题意:有n个字符串,任选k个回文子串,问其长度之和能否等于L。
题解:用manacher算法求出所有回文子串的长度,并记录各长度回文子串的个数,再用背包思想判断是否有解。
dp[i][j]:选取i个回文子串,长度之和是否为j
其中用到二进制分解思想,将回文串长为i的数量cnt[i]拆成1+2+4+8+…形式,进行优化。
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 #define CLR(a,b) memset((a),(b),sizeof((a))) 6 const int N = 105; 7 int dp[N][N], w[N*N*N], cnt[N], cnt1[N*N*N]; 8 char s[N*2]; 9 void Manacher(char s[],int len) { 10 char Ma[N*2]; 11 int Mp[N*2] , l = 0; 12 CLR(Mp, 0); 13 Ma[l++] = '$'; Ma[l++] = '#'; 14 for(int i = 0; i < len; i++) { 15 Ma[l++] = s[i]; Ma[l++] = '#'; 16 } 17 int mx = 0, id = 0; 18 for(int i = 0;i < l; i++) { 19 Mp[i] = mx > i ? min(Mp[2*id-i], mx-i) : 1; 20 while(Ma[i+Mp[i]] == Ma[i-Mp[i]]) Mp[i]++; 21 if(i + Mp[i] > mx) { 22 id = i; 23 mx = i + Mp[i]; 24 } 25 if(Ma[i] == '#'&& Mp[i] == 1) continue; 26 cnt[Mp[i]-1]++;//记录该回文串长度数量 27 } 28 } 29 int main(){ 30 int t, i, j, n, k, l, x, num; 31 scanf("%d", &t); 32 while(t--) { 33 scanf("%d%d%d", &n, &k, &l); 34 CLR(dp, 0); CLR(cnt, 0); 35 for(i = 0; i < n; ++i) { 36 scanf("%s", s); 37 int len = strlen(s); 38 Manacher(s, len); 39 } 40 num = 0; 41 for(i = 1; i <= 100; ++i) { 42 for(j = 1; j <= cnt[i]; cnt[i] -= j, j <<= 1) { 43 w[num] = j * i; 44 cnt1[num++] = j; 45 } 46 if(cnt[i]) { w[num] = j * i; cnt1[num++] = j; } 47 } 48 dp[0][0] = 1; 49 for(i = 0; i < num; ++i) 50 for(j = l; j >= w[i]; --j) 51 for(x = cnt1[i]; x <= k; ++x) 52 dp[x][j] |= dp[x-cnt1[i]][j-w[i]]; 53 if(dp[k][l]) puts("True"); 54 else puts("False"); 55 } 56 return 0; 57 }