NIO:7219:复杂的整数划分问题(DP)

NIO:7219:复杂的整数划分问题

复杂的整数划分问题题目链接
总时间限制:
200ms
内存限制:
65536kB

描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入
标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)

输出
对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目
样例输入
5 2
样例输出
2
3
3
提示
第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1

这道题的关键就是用动态规划的思想分别找到三个问题的递推关系,但是求完以后惊奇的发现第二和第三个问题的结果是一样的,我一时也没办法证明出来为啥。

1.N划分成K个正整数之和的划分数目:我们首先设一个二维数组dp1[i][j],代表将数字i分成j个正整数的划分数目,然后看看能不能找到子问题。苦思冥想(太难想了)之后我们就能发现,我们可以把问题分成,有1参与划分的情况加上没有1划分的情况,这显然是没有重复的两个情况。
边界:只要这个i它大于等于1,那如果要让他划分成1个数字,那就只有它自己,所以就只有一种情况,也就是dp1[i][1]=1;
倘若有1参与,那么情况将是dp1[i-1][j-1],因为我们可以让所有有1参与的情况中拿出一个1,就变成了将数字i-1分成j-1个正整数,他们的情况数目是一样的,只不过少一个1。
倘若没有1参与,那么情况将是dp1[i-j][j],因为我们可以让没有1的情况(也就是所有参与划分的数字起码都比1大的情况)中参与划分的数字都减去1,那么这个数字就减去了j,但是还是将它划分成了j个整数。

2.N划分成若干个不同正整数之和的划分数目:有些类似于01背包,不能重复取走物品。思路很像,但有地方会有区别。我们也设一个数组dp2[i][j],代表取i个数字,加起来正好是j的划分情况有多少。我们也同01背包一样考虑是否拿最后一个,在这里当然比较特殊,最后一个数字一定就是i了。
边界:我们这样想,倘若j==0的话,那不就很好了吗,那只有一种情况,什么都不用做的就对了!因为这里有个很重要的点就是,跟01背包一样,取前i个并不代表我就要都用上。所有我们可以dp2[i][0]=1;注意注意!:i=0的话也是要遍历的,因为就算我没有数字,但是又恰恰正好只要划分成0,所有我们可以一样什么都不做,也是一种情况。
倘若不取最后一个:那么很显然了,就是dp2[i-1][j]。
倘若取最后一个:那也很显然,就是dp2[i-1][j-i],那么这里我们肯定就是有条件的,那显然就是j要大于等于i,才会发生这种情况。那么有人看到这里可能会问,j要是太大,那就不成立了怎么办?一开始我写出这个递推式的时候我也怀疑是否要增加条件,后来我发现其实多此一举。j什么时候才会太大呢?那自然是将拿i前面所有的数字加起来都比j要小的时候就是无法成立的时候,也就是(1+i)*i/2(等差求和)<j。那么再看我们的递推式dp2[i][j]=dp2[i-1][j]+dp2[i-1][j-i],第一个不用说了,i-1比i更小了,那推出来的时候就肯定是0了,第二个把使dp2[i-1][i-j]不成立的条件列出来并一移项你就会发现它跟dp2[i][j]不成立的条件是一样的,那么dp2[i][j]如果不成立那就是0+0了,无需增加条件判断。

3.N划分成若干个奇正整数之和的划分数目思路和第一个问题相似,考虑有1还是没有1,只不过没有1的那部分有点区别。设一个二维数组dp3[i][j],代表将i划分成j个奇数。
边界:考虑j=1的情况,如果j等于1,那就是代表就取一个奇数,那只要i是奇数dp3[i][1]就是1了。
如果取了1:倘若取了1,那就是dp3[i-1][j-1]。
如果没有取1:如果没有取1,那应该是dp3[i-2j][j],不能和第一个问题一样都减去1,因为减去了那就全是偶数了,那全减去2就完事了,因为如果没有1的话,最小的奇数也是3。这里得注意条件,应该是i>=2j+j,因为在dp3[m][n]中,m是肯定要大于等于n的。

实现代码如下

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int n,k;
    while(cin>>n>>k)
    {
        int dp1[100][100]={0},dp2[100][100]={0},dp3[100][100]={0};
        for(int i=1;i<=n;++i)
            dp1[i][1]=1;
        for(int i=1;i<=n;++i)
        {
            for(int j=2;j<=k;++j)
            {
                if(i>=j){
                dp1[i][j]=dp1[i-1][j-1]+dp1[i-j][j];}
            }
        }
        cout<<dp1[n][k]<<endl;
        for(int i=0;i<=n;++i)
        {
            dp2[i][0]=1;
        }
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=n;++j)
            {
                    if(j<i)
                        dp2[i][j]=dp2[i-1][j];
                    else
                        dp2[i][j]=dp2[i-1][j]+dp2[i-1][j-i];
            }
        }
        cout<<dp2[n][n]<<endl;
        for(int i=1;i<=n;++i)
            if(i%2!=0)
            dp3[i][1]=1;
        for(int i=2;i<=n;++i)
        {
            for(int j=2;j<=i;++j)
            {
                    if(i>=2*j+j)
                        dp3[i][j]=dp3[i-1][j-1]+dp3[i-2*j][j];
                    else
                        dp3[i][j]=dp3[i-1][j-1];
            }
        }
        int sum=0;
        for(int i=1;i<=n;++i)
            sum+=dp3[n][i];
        cout<<sum<<endl;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值