Analysis
一棵树的每颗子树都对应着这棵树 DFS 序的一个区间。本题的序列虽然不是 DFS 序列,但也有该性质。本题中,我们以区间长度作为阶段, F[ l , r ] 表示序列 s[ l ~ r ]中子树的个数。
如果我们从 l 到 r 在每一个点划分一个 k ,那么时间复杂度会很高。一个比较好的想法是,把子串s[ l ~ r ]分成两部分,每部分可由若干子树构成。为了计数重而不漏,我们只考虑子串的第一颗子树是由哪些序列构成的,即令子串s[ l+1 ~ k-1 ] 构成第一棵子树,s[ k~r ]构成剩余部分。
为什么这样不会重复呢?因为我们 k 不断向后移动,序列不断加长,也就是说第一颗子树规模在从小变大,当然不会重复;至于剩下部分构成的子树,同理,由于规模不断减小,不可能重复。
为什么还要加上一个F[ l + 1 , r - 1] 呢?因为我们虽然枚举了第一颗子树,但是却忽略了该树只有一颗子树的情况,所以需要再加上这种情况,即F[ l + 1 , r - 1 ]。
对于方案计数类的动态规划问题,通常一个状态的各个决策之间满足“加法原理”,而每个决策划分的几个子状态之间满足“乘法原理”。在设计状态转移方程的决策方式与划分方法时,一个状态的所有决策之间必须具有互斥性,才能保证不会出现重复问题。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<stack> 6 #define int long long 7 #define maxn 300+10 8 #define mod 1000000000 9 using namespace std; 10 inline int read() 11 { 12 int x=0; 13 bool f=1; 14 char c=getchar(); 15 for(; !isdigit(c); c=getchar()) if(c==‘-‘) f=0; 16 for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+c-‘0‘; 17 if(f) return x; 18 return 0-x; 19 } 20 inline void write(int x) 21 { 22 if(x<0){putchar(‘-‘);x=-x;} 23 if(x>9)write(x/10); 24 putchar(x%10+‘0‘); 25 } 26 int len; 27 int dp[maxn][maxn]; 28 char s[maxn]; 29 inline int DP(int l,int r) 30 { 31 if(l>r) return 0; 32 if(s[l]!=s[r]) return 0; 33 if(l==r) return 1; 34 if(dp[l][r]!=-1) return dp[l][r]; 35 dp[l][r]=0; 36 for(int k=l+1;k<=r-1;k++) 37 { 38 dp[l][r]=dp[l][r]+(DP(l+1,k)*DP(k+1,r))%mod; 39 dp[l][r]%=mod; 40 } 41 return dp[l][r]; 42 } 43 signed main() 44 { 45 // freopen("pyramid.in","r",stdin); 46 // freopen("pyramid.out","w",stdout); 47 memset(dp,-1,sizeof(dp)); 48 scanf("%s",s+1); 49 len=strlen(s+1); 50 int ans=DP(1,len); 51 ans%=mod; 52 write(ans); 53 return 0; 54 }
请各位大佬斧正(反正我不认识斧正是什么意思)