自然数拆分(完全背包问题求方案数)

给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复。

注意:

  • 拆分方案不考虑顺序;
  • 至少拆分成2个数的和。

求拆分的方案数 mod 2147483648的结果。

输入格式

一个自然数N。

输出格式

输入一个整数,表示结果。

数据范围

1≤N≤4000

输入样例:

7

输出样例:

14

思路:将一个数差分成若干个数,很显然其中某些数可能会出现多次, 就是说这些数可以用无限多次来构成最初的数,这就是完全背包问题:有n个物品,每个物品可以选无限多次,求选出的若干个物品的价值之和恰好为n的方案个数。

状态表示:用f[i,j]表示选前i个物品且总价值之和恰好为j的方案,维护的属性:count。

状态计算:将f[i,j]按当前i(集合的最后一件物品)选和不选划分为两部分,前一部分:f[i-1,j];

后一部分:因为每件物品可能选多次,所以还要考虑次数,所这一部分方案数为:f[i,j-vi]+f[i,j-2vi]+...

因此,总的方案数为f[i,j]=f[i-1,j]+(f[i,j-vi]+f[i,j-2vi]+...),但是这样就要再枚举每件物品选的次数,时间复杂度变成o

(n^3),所以要再进一步优化:

通过对比观察f[i,j]和f[i,j-vi]代入上式的结果发现:f[i,j]=f[i-1,j]+f[i,j-vi]+f[i,j-2vi]+...

                                                                        f[i,j-vi]=f[i-1,j-vi]+f[i,j-2vi]+...

所以,通过等价代换可得:f[i,j]=f[i-1,j]+f[i,j-vi]

所以每次当前状态i与i这一状态有关,所以只要在循环体积时,从小到大枚举,就可以保证每次状态都是新的,恰好达到统计效果。

完整代码:

#include <iostream>

using namespace std;

const int maxn=4e3+5,mod=2147483648;

unsigned f[maxn];

int main()
{
    int n;
    cin>>n;
    f[0]=1;
    for(int i=1;i<=n;i++){//枚举物品(最初的自然数)
        for(int j=i;j<=n;j++){//枚举体积(分解的每个数)
            f[j]+=f[j-i];
        }
    }
    cout<<(f[n]-1)%mod<<endl;//因为至少要拆成两个数,所以1个的不算,要-1
    return 0;
}
发布了176 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Mr_Kingk/article/details/105690290