首页 > 代码库 > dp1221
dp1221
类似于整数分解,但要求分解得到的式子是回文,且前半部分单调非递减,后半部分单调非递增
1: (1)
2: (2), (1 1)
3: (3), (1 1 1)
4: (4), (1 2 1), (2 2), (1 1 1 1)
5: (5), (1 3 1), (1 1 1 1 1)
6: (6), (1 4 1), (2 2 2), (1 1 2 1 1), (3 3),
(1 2 2 1), ( 1 1 1 1 1 1)
7: (7), (1 5 1), (2 3 2), (1 1 3 1 1), (1 1 1 1 1 1 1)
8: (8), (1 6 1), (2 4 2), (1 1 4 1 1), (1 2 2 2 1),
(1 1 1 2 1 1 1), ( 4 4), (1 3 3 1), (2 2 2 2),
(1 1 2 2 1 1), (1 1 1 1 1 1 1 1)
首先要说明的是此题结果会很大。开始用的long long 提交后WA ,后来换成__int64过了。
而第二个问题就是重点了。令 f(n,m) 表示 n 的划分 P = {n1, n2, ..., nk | k>= 1, 0 < ni <= m for 1<=i <= k, n1+n2+...+nk = n} 的个数,M表示所有的构成因子都必须小于等于M,则我们有递归方程式
1) m == 1,则 f(n,m) = 1,也就是只有 {1, 1, ... 1 } 这种划分;
2) m==n,则 f(n,m) = f(n, m-1) + 1,也就是除了划分 {n},在其它的划分中,元素都小于或等于 m-1;
3) 否则,f(n,m) = f(n,m-1) + f(n-m, min(n-m,m))。分为最大不超过m-1的情况加上因数有一个为m的情况(后一种情况里,可能有一个M或者两个,但肯定有一个,有可能不是两个同时存在;另外后一个情况里,存在一个M后,所有因子必须小于M,这是显然,但还必须小于N-M)。
也就是把n分为因数最大不超过m的分法有多少种。这就是此题的核心。把这个弄清楚了之后我是分两种情况讨论的。即奇数和偶数。不论奇数还是偶数都是看中间的数。两边的数列是以中间的数中心对称的且都小于中间的数。这里的DP都是仅仅考虑最中间数的一边的情况,并不是两边同时考虑。仅仅考虑一边的数从大到小排列的情况数
dp[i][j] = dp[i][j-1] + dp[i-j][min(i-j,j)];这个公式本身就考虑到了序列顺序的问题(必须上升再下降)
比如dp[8][6]=dp85+dp22,dp85=dp84+dp33,dp84=dp83+dp44,dp83=dp82+dp53,可见 f(n,m-1)其实是逐渐下降的,而dp[i-j][min(i-j,j)],其实是表示中间的元素首选取J,(可能有一个,对于奇数情况;可能是两个,偶数情况),比如dp82=dp81+dp62,dp62=dp61+dp42,dp42=dp41+dp22.也就是说dp[i-j][min(i-j,j)]是从大到小递减的,dp62表示8里面已经取了2(最大值,在中间,看做xxx2xxx),dp42表示和为6的序列中间取最大值2(此时序列为xx22xx),dp22表示和为2的序列再取最大值2,此刻dp22表示序列为xxx222xxx形式,能加的元素最大不超过2,和为2,共有两种可能12221,2222。
总结一下,即dp[i][j] = dp[i][j-1] + dp[i-j][min(i-j,j)];前半部分是通过j-1逐渐减一,得到全为1的可能,它的意思在于最大值不超过j-1.后半部分表示必须存在最大值j,且把它放在中间位置(当然可能取值为0)
#include <iostream>#include <algorithm>#include <cmath>using namespace std;__int64 dp[301][301];int main() { int a; for (int i = 1;i <= 300;i++){ dp[i][0] = 0; dp[0][i] = 0; dp[i][1] = 1; for (int j = 1;j <= 300;j++) { if(i == j) dp[i][j] = dp[i][j-1] + 1; else if(i < j) dp[i][j] = dp[i][i]; else dp[i][j] = dp[i][j-1] + dp[i-j][min(i-j,j)]; } } while(cin >> a && a){ __int64 s = 1;//只有一个构成元素A的情况 必须单独考虑因为下面的公式a-i=0时,返回0 if(a%2) //奇数的时候 for(int i = 1;i < a;i+=2){ //奇数必定存在于中间的各个情况,如果中间奇数为i则所有值必须小于i s += dp[(a-i)/2][i];//i表示最中间的数大小 else { s += dp[a/2][a/2];//中间偶数为0,则两边的数列和都为a/2,极限情况为(a/2 |a/2) for(int i = 2;i < a;i+=2) //偶数存在于中间的各个情况 s += dp[(a-i)/2][i]; } cout <<a<<" "<<s <<endl; } return 0;}