HDU 6092 Rikka with Subset 【dp多重背包】【好题】

本文探讨了一个有趣的数学问题,即如何从给定的子集求和频次逆向推导原始数组。通过分析子集求和的特性,文章提出了一种高效算法来解决这一问题,并附带代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



Rikka with Subset

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 139    Accepted Submission(s): 49


Problem Description
As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some math tasks to practice. There is one of them:

Yuta has  n  positive  A1An  and their sum is  m . Then for each subset  S  of  A , Yuta calculates the sum of  S

Now, Yuta has got  2n  numbers between  [0,m] . For each  i[0,m] , he counts the number of  i s he got as  Bi .

Yuta shows Rikka the array  Bi  and he wants Rikka to restore  A1An .

It is too difficult for Rikka. Can you help her?  
 

Input
The first line contains a number  t(1t70) , the number of the testcases. 

For each testcase, the first line contains two numbers  n,m(1n50,1m104) .

The second line contains  m+1  numbers  B0Bm(0Bi2n) .
 

Output
For each testcase, print a single line with  n  numbers  A1An .

It is guaranteed that there exists at least one solution. And if there are different solutions, print the lexicographic minimum one.
 

Sample Input
      
      
2 2 3 1 1 1 1 3 3 1 3 3 1
 

Sample Output
      
      
1 2 1 1 1
Hint
In the first sample, $A$ is $[1,2]$. $A$ has four subsets $[],[1],[2],[1,2]$ and the sums of each subset are $0,1,2,3$. So $B=[1,1,1,1]$
 

Source

题意:有一个数列 a[] ,长度(n<=50)。b[i] 表示元素和为 i 的集合个数。给你一个数列 b[] ,长度(m<=10000),让你求 a[],并按照其字典序最小输出


显然数字0的数量num[0]为log2(b[0]),数字1的数量num[1]为b[1]/b[0]

设dp[i],表示在当前i没有的情况下,用前面已知数量的数组成数字i共有多少种情况

那么b[i]-dp[i]即为数字i与0进行组合的可能性,则num[i]=(b[i]-dp[i])/b[0]

这里如果直接写的话复杂度为o(m^2)会超时,所以需要剪枝:

if (dp[j] == 0) continue;
if (num[i] == 0) break;
直接将m^2的复杂度降为nm

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <map>
#define ms(a,b) memset(a,b,sizeof(a)) 
using namespace std;
typedef long long ll;

const int maxn = 1e4 + 100;

ll b[maxn];
int dp[maxn], num[maxn];

int C(int n, int m)
{
	int sum = 1;
	for (int i = n - m + 1; i <= n; i++) sum *= i;
	for (int i = 1; i <= m; i++) sum /= i;
	return sum;
}

int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n, m;
		scanf("%d%d", &n, &m);
		ms(dp, 0);
		ms(num, 0);
		for (int i = 0; i <= m; i++)
		{
			scanf("%lld", &b[i]);
		}
		num[0] = log2(b[0]);
		num[1] = b[1] / b[0];
		dp[0] = b[0];
		for (int i = 0; i <= m; i++)
		{
			for (int j = m; j >= 0; j--)
			{
				if (dp[j] == 0) continue;
				if (num[i] == 0) break;
				for (int k = 1; k <= num[i]; k++)
				{
					if (j + k*i <= m)
					{
						dp[j + k*i] += dp[j] * C(num[i], k);
					}
				}
			}
			if (i + 1 <= m)
			{
				num[i + 1] = (b[i + 1] - dp[i + 1]) / b[0];
			}
		}
		bool flag = 0;
		for (int i = 0; i <= m; i++)
		{
			for (int j = 1; j <= num[i]; j++)
			{
				if (!flag) printf("%d", i), flag = 1;
				else printf(" %d", i);
			}
		}
		puts("");
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值