首页 > 代码库 > 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) 表示 的划分 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;}