数的拆分专题

情况一:

将一个数拆分为2的k次方的和的拆分方式和,例如,4可以拆分为1 +1+1+1, 1+1+2, 2+2, 4,一共有四种拆法。

我们定义数组dp[i]为 数 i的拆分方式总数。通过分析,对于一个奇数来说,他的拆分总数和比它小一位的偶数是相同的,即dp[i]=dp[i-1];对于偶数,我们分为两种情况,当偶数的拆分里没有1时,那么显然dp[i]=dp[i/2],因为每一位拆分的项数都可以除以二,当偶数的拆分里有1时,则有dp[i]=dp[i-1],因为必然有1故减去1不影响总数。根据加法原理,可以得到递推式:

i为奇数,dp[i]=dp[i-1]; i为偶数,dp[i]=dp[i/2]+dp[i-1];

这题用递推的方法巧妙地实现了数与数之间的联系。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[1000];
int main()
{
    int n;
    cin>>n;
    dp[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(i%2==0)dp[i]=dp[i/2]+dp[i-1];
        else dp[i]=dp[i-1];
    }
    cout<<dp[n];
}

例题链接洛谷1734最大约数和
题目的描述里,需要找到和不超过S的若干个数,使其约数和最大。对于小于S的每一个数而言,其约数和是固定的,就像有固定的价值。这样,就转化为了一个01背包问题。背包的容量大小为S,将小于S的每一个数的值看做消耗,得到的价值为其约数。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int T[1005];
int dp[1005];
int main()
{
    int n;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        T[i]=1;
        for(int j=2;j*j<=i;j++)
        {
            if(i%j==0)
            {
                T[i]=T[i]+j;
                if(j*j!=i)T[i]=T[i]+i/j;
            }
        }
    }
    T[1]=0;
    for(int a=1;a<=n;a++)
    {
        for(int b=n;b>=a;b--)
        {
            dp[b]=max(dp[b],dp[b-a]+T[a]);
        }
    }
    cout<<dp[n];
}

注意1的约数和为0。

情况三:
例题链接
对于每一个数而言,他的素数和表达式里,如果单独地看其中的某一个素数,就会成为另一个数的素数表达式再加上一个素数。这样,对于一个数 n,我们可以枚举所有不超过它的素数 prime,假如k<n且k+prime=n,则n的种数要加上k的种数。

为了使答案正确,我们先逐个枚举素数,然后更新 k+素数 的值。这样就模拟了类似于 2+2+2+3,2+2+5…这样的拆分过程,避免了重复计数。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int check[1005],prime[1005];
int tot=0;
long long ans[1005];
void get(int n)
{
    for(int i=2;i<=n;i++)
    {
        if(!check[i])
        {
            prime[tot]=i;
            tot++;
        }
        for(int j=0;j<tot&&prime[j]*i<=n;j++)
        {
            check[prime[j]*i]=1;
            if(i%prime[j]==0)break;
        }
    }
}
int main()
{
    int n;
    cin>>n;
    get(n);
    ans[0]=1;//初始化很重要,相当于让素数本身代表的数加1
    for(int i=0;i<tot&&prime[i]<=n;i++)
    {
        for(int j=0;j<=n;j++)
        {
            if(ans[j]&&(j+prime[i]<=n))ans[prime[i]+j]+=ans[j];
        }
    }
    cout<<ans[n];
}

再用动态规划的角度去看待,则是一个类似于完全背包的问题,因为物品即素数是任取的且无限多的,背包大小为当前数,得到的价值就为可以新增的方式数。转移方程为:

dp[i] = dp[i] + dp[ i-prime[j] ] prime[j]为小于 i 的可能的素数。贴一下关键代码:

for(int i=0;i<tot;i++)
    {
        for(int j=prime[i];j<=n;j++)
        {
            dp[j]+=dp[j-a[i]]; //更小的背包已更新完,再更新当前背包
        }
    }

情况四:
例题链接
该题是情况三的一种拓展。对于一个数,要将其分为不多于四个的平方数的和,例如25=12+22+22+42,求有多少种不同的分法。

该题的理解过程和情况三类似,对于任意一个数,可以有状态转移方程 dp[k] = dp[k] + dp[k-i*i](和素数的拆分基本一致,相当于找更小规模的数),需要注意的是限制条件,所以改用二次数组记录 dp[h][k],k用于记录已有的项数。状态转移方程为:

dp[h+i* i][k]=dp[h+i*i][k]+dp[h][k-1]

注意要把项数一到四的情况全部枚举更新。初始化dp[0][o]=1。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int dp[32770][5];
int main()
{
    int n;
    cin>>n;
    int maxn=32768;
    dp[0][0]=1;
    for(int i=1;i*i<=maxn;i++)
    {
        int h=i*i;
        for(int j=0;j<=maxn;j++)
        {
            if(dp[j]&&j+h<=maxn)
            {
                for(int k=1;k<=4;k++)
                {
                    dp[j+h][k]+=dp[j][k-1];
                }
            }
        }
    }
    while(n--)
    {
        int num;
        cin>>num;
        int ans=dp[num][1]+dp[num][2]+dp[num][3]+dp[num][4];
        cout<<ans<<endl;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值