• POJ 7219 复杂的整数划分问题 【dp】【北大ACM/ICPC竞赛训练】


    非常难想的题,感觉如果是第一次做的话很难做出来了。

    这里问的其实有三个问题,而且相对独立,所以要分开求解。

    1.整数n拆成k个正整数

    dp[i][j]代表整数 i 拆成k个正整数有多少拆法,那转移方程是dp[i][j] = dp[i-j][j] + dp[i-1][j-1]

    这一点我觉得基本上是想不到的。我们转移的时候把所有用j个数组成i的方案分成两类,一类是这j个数里面有至少一个1;另一类是这j个数里面都大于1。那如果这j个数都大于1,那问题就等价于dp[i-j][j](把所有方案对应的j个整数都减一就行了),再考虑至少有一个1的话那就拿走一个1变成dp[i-1][j-1]。

    这里有一点很巧妙是避免了重复的问题,比如把5拆成1+3+1和1+1+3算成两种。

    它这么转移保证了不会重复,因为有1的方案一定与没有1的方案不重复!

    2.挑若干个不同正整数凑成n

    这是个01背包问题,前提是能看出来

    3.挑若干个正奇数凑成n

    dp[i][j]代表前i个数拆成j个奇数的拆法。跟整数拆分相似,考虑有没有1。不过没有一的话我们要减2*j而不是j,因为没有1的话,最小的就是3往上了

    边界条件是dp[i][1],i为奇数那就是1,为偶数就是0

    那dp[i][j] = dp[i-2*j][j] + dp[i-1][j-1]

    ===2018.9.7===

    为什么不能像1里面是dp[i-j][j]呢,因为原来的dp[i][j]是用j个奇数组成i,然后我们枚举最小的组成i的数是不是1,如果是的话,那就所有的都减1,但因为都是用奇数凑的,都减一的话就都剩偶数了,那dp[i-j][j]应该是用j个偶数凑成i-j,这样才与原问题是等价的。但用dp[i-2*j][j]就很好的解决了这个问题。(核心就是通过考虑最小的有没有1来将问题化简成一个范围更小的等价问题。【与以前做的大多数dp不一样,以前的是强调上一个状态是怎么【转移】过来的,但这一题是强调等价问题,也是这题难的地方】)

    或者说转移的核心就是等价

     1 #include<iostream>
     2 #include<cstring>
     3 using namespace std;
     4 
     5 int memo1[55][55],memo2[55][55],cost[55],dp[55][55];
     6 
     7 int dp1(int n,int k){//整数为n拆成k份
     8     if(memo1[n][k]!=-1) return memo1[n][k];
     9     if(n==k) return memo1[n][k]=1;
    10     if(k==1) return memo1[n][k]=1;
    11     if(n>=k+k) return memo1[n][k]=dp1(n-k,k)+dp1(n-1,k-1); 
    12     return memo1[n][k]=dp1(n-1,k-1);
    13 }
    14 
    15 int dp2(int n,int choose){//从前choose个整数里挑,凑成n 
    16     if(n==0) return memo2[n][choose]=1;
    17     if(choose==0) return memo2[n][choose]=0;
    18     if(n>=cost[choose]) return memo2[n][choose]=dp2(n-cost[choose],choose-1)+dp2(n,choose-1);
    19     return memo2[n][choose]=dp2(n,choose-1);
    20 }
    21 
    22 
    23 int main(){
    24     
    25     for(int i=1;i<55;i++) cost[i]=i;
    26     
    27     int n,k;
    28     while( scanf("%d",&n)!=EOF ){
    29         memset(memo1,-1,sizeof(memo1));
    30         cin>>k;
    31         cout<<dp1(n,k)<<endl;//问题1
    32         cout<<dp2(n,n)<<endl;//问题2 
    33         //N划分成若干个奇正整数之和的划分数目
    34         //dp[i][j]代表i划分成j个奇正整数之和的数目
    35         for(int i=1;i<=n;i++){
    36             if(i%2) dp[i][1]=1;
    37             else dp[i][1]=0;
    38         }
    39         
    40         for(int i=2;i<=n;i++){
    41             for(int j=2;j<=i;j++){
    42                 if( i>=2*j+j ) dp[i][j] = dp[i-2*j][j]+dp[i-1][j-1];
    43                 else dp[i][j]= dp[i-1][j-1];
    44             }
    45         }
    46         int cnt=0;
    47         for(int i=1;i<=n;i++) cnt+=dp[n][i];
    48         cout<<cnt<<endl;
    49     }
    50 
    51     return 0;    
    52 }
  • 相关阅读:
    WPF 下两种图片合成或加水印的方式(转载)
    Git reset与checkout的区别
    C# 串口导致电脑蓝屏一个可能的原因
    两个用于win7任务栏显示进度的dll
    批量远程执行shell命令工具
    基于zookeeper的主备切换方法
    代码的演变随记
    svn错误:Can't convert string from 'UTF-8' to native encoding
    not allowed to access to crontab because of pam configuration
    大数据利器
  • 原文地址:https://www.cnblogs.com/ZhenghangHu/p/9393722.html
Copyright © 2020-2023  润新知