POJ2229 Sumsets 动态规划

本文探讨了一种高效方法来计算一个给定整数N的2的幂组合方式数量,其中N的范围在1到1000000之间。通过构建递归树模型并应用动态规划技巧,实现复杂度为O(n)的算法解决方案。

Description

Farmer John commanded his cows to search for different sets of numbers that sum to a given number. The cows use only numbers that are an integer power of 2. Here are the possible sets of numbers that sum to 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 

Help FJ count all possible representations for a given integer N (1 <= N <= 1,000,000). 
        题目大概意思就是给一个数N,然后将它分成若干的2的幂组成的和,问有多少种不同的分法。
这题目通过率还是比较高,可能方法也比较多。我就详细地说一下我自己用的方法,复杂度O(n)。
        首先,为了避免重复计数,定义一下C(i)表示分法中最大数为i的总共方法数,最终结果S(N) = C(1) + C(2) + C(4) +....+ C(2 ^ x)。
现在只要求出C(i)就能得到最终答案.
用一棵树来表示一下。
(没有用专门的作图软件,排版也比较乱。)
        解释一下图的含义:这个图是N=7时的例子图,设每个节点的深度为d,节点的值为v,就是代表最大值为2^d出现了V次。根节点的深度为0,所以根节点的值就为N,因为划分的元素必须是2的次幂,所以可以知道每一次的划分都是在上一次划分的基础上选择两个相同的值相加后得到新的划分。
        所以N=7时,根节点为7代表最大值为1出现了7次,也就是最初始的划分,元素全是1,接着我们想得到最大值为2的划分分案,即在此基础上,分别选择2个,4个,6个1进行两两相加得到1个2,2个2,3个2的划分分案,就是图中的第二层节点。至此,就可以很清楚的知道S(N)的值就是求类似树的所有节点数,看图就知道S(N)是具有递归性质的,很明显的,S(N) = S(1) + S(2) +...+ S(N/2) + 1,最后的+1代表本身节点,初始值S(1) = 1,S(2) 。如果直接用递归算法简单求解的话会有很多重复计算,计算量太大,DP的基础。。呵呵。
        这个公式作个简单的变形就OK了,如果N为奇数,S(N) =S(N-1);如果N为偶数,S(N) = S(N - 1) + S(N/2)。直接看代码吧。
#ifndef HEAD
#include <stdio.h>
#include <vector>
#include <math.h>
#include <string.h>
#include <string>
#include <iostream>
#include <queue>
#include <list>
#include <algorithm>
#include <stack>
#include <map>

using namespace std;
#endif // !HEAD

#define MODDDDDD 1000000000

long long sumset1[500001];

inline long long calcsumset(int N)
{
	if (N == 1)
	{
		return 1;
	}
	else if (N==2)
	{
		return 2;
	}
	memset(sumset1, 0, sizeof(sumset1));
	sumset1[1] = 1;
	sumset1[2] = 2;
	long long ires = 3;
	int lasthalf = 1;
	for (int i = 3; i <= N / 2;i++)
	{
		if (i / 2 == lasthalf)
			sumset1[i] = sumset1[i - 1];
		else
			sumset1[i] = sumset1[i - 1] + sumset1[i / 2];
 		sumset1[i] %= MODDDDDD;
		ires += sumset1[i];
		ires %= MODDDDDD;
		lasthalf = i / 2;
	}
	return (ires + 1) % MODDDDDD;
}

int main()
{
#ifdef _DEBUG
	freopen("d:\\in.txt", "r", stdin);
#endif
	int n;
	scanf("%d\n", &n);
	printf("%d\n", calcsumset(n));
return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值