这个问题可以先处理处一个数组A来
其中A[i] 代表长度为i的回文串有多少个。
那么定义状态d[i][j]代表当前已经选了i个总长度为j这个状态是否可达。问题就是个多重背包。
但是当时做的时候采用了另一个思路,定义d[ i ] [ j ] [ k ] 代表当前只考虑到长度为i 及以上的 回文串选择情况时,当前还剩余j个没有选,需要这j个总长度为k;
那么,最普通的转移就是o(n)就是枚举i长度的选了几个,然后转移到i+1对应的状态,并且这样转移没有办法优化,
那么,我们这样来考虑问题当前还有j个串没有选,不管这一次选了几个长度为i的串,这k个串的长度都将>=i , 我们可以预先将着k个长度都减去1,因为这个1迟早会交付。
所以不管这一次选了几个当前长度的回文串,转移必将如此, d[ i ][ j ][k] = d[i][j][k] | ( d[i + 1][ j - x ] [ k - j ])( x为这次选了几个长度为i的回文串), 那么转移到的状态是第三维恒为定值的一个连续区间,那么就可以通过预处理i + 1 状态的区间和,来更新i的状态,做到O(1)的转移
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> using namespace std; typedef long long ll; #define rep(i,n) for(int i =0;i<(int)n;i++) #define rep1(i , x, y) for(int i = (int)x;i<=(int)y;i++) const int N = 105; char s[N]; int a[N]; int d[N][N]; int max_ = 0; void cal(){ int n = strlen(s); for(int len = 1 ; len <= n ; len ++) for(int i = 0; i + len <= n; i++){ int j = i + len - 1; if(len == 1) d[i][j] = 1; else if(len == 2){ d[i][j] = (s[i] == s[j]); } else { d[i][j] = ((s[i] == s[j]) & d[i + 1][j - 1]); } if(d[i][j]){ a[len]++; max_ = max(max_ , len); } } } int n , K , L; bool d2[N][N][N]; int main() { int T; scanf("%d",&T); while(T--){ scanf("%d %d %d",&n ,&K , &L); memset(a , 0, sizeof(a)); max_ = 0; for(int i =1 ; i<=n;i++){ scanf("%s",s); cal(); } int sum[N][N]; for(int i = max_ + 1 ; i >= 1; i--){ for(int j = 0 ; j<= K; j++) for(int k =0 ; k <= L; k++){ if(i == max_ + 1){ if(j == 0 && k == 0) d2[i][j][k] = true; else d2[i][j][k] = false; } else { if(j > k) d2[i][j][k] = false; else { int l = max(0 , j - a[i]) , r= j; int all = sum[k - j ][r] - (l ==0 ? 0 : sum[k - j][l - 1]); d2[i][j][k] = (all > 0); } } } memset(sum , 0, sizeof(sum)); for(int k = 0 ; k<=L; k++) for(int j = 0 ; j<=K; j++) sum[k][j] += (j-1 < 0 ? 0 :sum[k][j-1]) + d2[i][j][k]; } printf("%s\n",d2[1][K][L] ? "True" : "False"); } return 0; }