POJ2229---Sumsets(数的划分,dp,透彻地讲解两种递推方法)

来源:互联网 发布:淘宝网品牌男装 编辑:程序博客网 时间:2024/05/22 13:39
题意:用1,2,4,8,16,32,~~~ 2^n 。 去组合一个数。
方法①:
dp[i]:i的划分数。
dp[i]=dp[i-1]                //i为奇数
dp[i]=dp[i-1]+dp[i/2]    //i为偶数
边界控制:dp[1]=1


当i为奇数
很好理解,奇数是1产生的吧,每个划分里面必须都要有1,把这个1减去,就是偶数i-1的划分。把7的每个划分都减去1不就是6的吗?
i=7
1) 1+1+1+1+1+1+1
2) 1+1+1+1+1+2
3) 1+1+1+2+2
4) 1+1+1+4
5) 1+2+2+2
6) 1+2+4


当i为偶数,把所有的划分 分成两组,带1的和不带1的。带1的不又是比原偶数(6)小1的奇数(5)的划分了吗?不带1的划分里面肯定含有2吧?同时除2,不又产生1了吗(也就是说,又是一个完整的划分了)?
i=6
1) 1+1+1+1+1+1
2) 1+1+1+1+2
3) 1+1+2+2
4) 1+1+4
5) 2+2+2    (1+1+1)
6) 2+4        (1+2)不就是3的所有(完整性)划分了吗?

总之,这种递推关系式一定要紧扣定义去递推!!!推出来的子问题不能有重合,更需要是一个完整的子问题!




方法2:
dp[i][j]:只用前i个数,j的组合数

dp[i][j]=∑dp[i-1][j-k*w[i]]   k>=0;
含义:先从前i-1个数里面组合成j-k*w[i].再从第i个里面拿出来k个。有点完全背包的意思。
化简:
原式=∑dp[i-1][j-k*w[i]]                      k>=0;
    =dp[i-1][j]+∑dp[i-1][j-k*w[i]]           k>=1;
    =dp[i-1][j]+∑dp[i-1][j-w[i]-(k-1)*w[i]]  k-1>=0;
    =dp[i-1][j]+dp[i][j-w[i]]

dp[i][j]=dp[i-1][j]+dp[i][j-w[i]]

边界控制dp[1][j]=1;

可以利用一维数组,但是注意第二个循环要顺序走。



AC代码1:

#include<iostream>#include<cstdio>#include<algorithm>using namespace std;long long int dp[1000010];const long long MOD = (int)1e9;int main(){int N; while (cin >> N){dp[1] = 1;for (int i = 2; i <= N; i++){if (i & 1)dp[i] = dp[i - 1];else dp[i] =( dp[i - 1] + dp[i / 2]) % MOD;}cout << dp[N]<< endl;}system("pause");}


AC代码2:

#include<iostream>#include<cstdio>#include<algorithm>using namespace std;long w[21];//2的20次方刚好比最大数据多一点int dp[1000005];const long long MOD = (int)1e9;int main(){int N;w[1] = 1;for (int i = 2; i <= 20; i++)  //20个物品,最后一个为2^19;w[i] = w[i - 1] * 2;while( cin >> N){fill(dp, dp + N + 1, 1);for (int i = 2; i <= 20; i++)for (int j = w[i]; j<=N; j++){dp[j] += dp[j - w[i]];dp[j] %= MOD;}cout << dp[N] << endl;}system("pause");}




原创粉丝点击