zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    IIS7中的几种身份鉴别方式(一)Basic身份验证
    IIS7中的几种身份鉴别方式(二)集成身份验证
    java集合
    SharePoint 2010中welcome page的设置细节
    SharePoint中使用Linq出现未将对象引用到实例化的解决方法
    SharePoint 2010中关于An error was encountered while retrieving the user profile的处理方式记录
    The Need for an Architectural Body of Knowledge
    The Softer Side of the Architect
    Event Receivers 学习小结
    使用SmtpClient发送带图片的邮件的代码实现
  • 原文地址:https://www.cnblogs.com/ZhenghangHu/p/9393722.html
Copyright © 2011-2022 走看看