给定一个自然数N,要求把N拆分成若干个正整数相加的形式,参与加法运算的数可以重复。与“自然数拆分问题”类似,同样需要满足方案的不重复。
所谓拆分方式的重复性判定如下:给定N=a
1
+a
2
+…a
m1
和 N=b
1
+b
2
+…b
m2
表示整数N的两种拆分方式。对于∀a
i
,b
j
≥1,令集合A={a
i
∣1≤i≤m
1
},B={b
j
∣1≤j≤m
2
}。若满足集合A=B,则称这两种拆分方式是重复的。
例如 6 = 3 + 2 和 6 = 2 + 3, 就是重复的拆分方式。
现在需要你求满足条件的拆分有多少种?
输入格式:
第一行自然整数T,表示之后测试数据组数, 以后T行,每行一个自然数N,(1<N<=4000)
注意:本题规模为4000,回溯法还合适吗?仔细思考应该如何设计算法
建议自行学习完全背包相关知识
输出格式:
T行,每行输出一个整数,表示拆分的方案数,结果对2147483648取模。
输入样例:
在这里给出一组输入。例如:
2
6
7
输出样例:
在这里给出相应的输出。例如:
11
15
分析:
虽然可以用回溯法去解决,但是当规模大的时候,回溯法需要的时间太长。就使用完全背包知识。
思路:
将一个数差分成若干个数,很显然其中某些数可能会出现多次, 就是说这些数可以用无限多次来构成最初的数,这就是完全背包问题:有n个物品,每个物品可以选无限多次,求选出的若干个物品的价值之和恰好为n的方案个数。
在完全背包中 状态转移方程
dp[i][k] = dp[i][k - 1] + dp[i - num[k]][k];
很明显,两者如出一辙,只是变成了一维。
#include<bits/stdc++.h>
using namespace std;
const int Max = 4001;
const int mod = 2147483648;
int n;
void oper(){
long long dp[Max]={0};
cin >> n;
dp[0] = 1;
for(int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
dp[j] = (dp[j] + dp[j - i]) % mod;
}
}
cout << dp[n]<<endl;
}
int main () {
int t;
cin>>t;
while(t>0){
oper();
t--;
}
return 0;
}