做题笔记:NOIP2018 货币系统

截至目前见过的最妙背包问题。

藏的真的很深。

有必要为此写一篇笔记。

题意

给你一个货币系统 \(A\) ,其中包含 \(n\) 种不同面值的货币,第 \(i\) 种货币面值为 \(a_i\)

现在让你求另一个货币系统 \(B\) ,满足所有可以用 \(A\) 中货币凑出来的金额也可以用 \(B\) 中的货币凑出来。

同样地, \(A\) 中货币凑不出来的金额,用 \(B\) 中的货币也凑不出来。

用样例举例:\(A\) 中种有3元,6元,10元,19元四种货币,那么由3元,10元组成的 \(B\) 货币系统满足条件。

要让 \(B\) 中的货币面值种类尽可能少,求最少的货币面值种数。

思路

某些大学生可能会想到一个叫线性代数的东西,但是由于本人太蒻,不予解说,直接讲正解。

这题第一步确实比较玄学,就是大胆地猜测 \(B\) 中的货币面额取自 \(A\)

然后考虑对其证明。

证明

不妨设 \(B\) 中存在一个面额 \(b_i\) 不属于 \(A\)

由题意知,不能存在 \(B\) 中能表示而 \(A\) 中无法表示的金额。

所以 \(b_i\) 显然必须\(A\) 中的若干种货币表示出来

又由题意知, \(A\) 中能表示的金额必须也能由 \(B\) 中货币表示出来

所以每个 \(a_i\) 都能被 \(B\) 中的其他货币表示。

因为\(b_i\)\(A\) 中的若干种货币表示出来,而 \(A\) 中货币又能被 \(B\) 中货币表示出来,最后归根到底, \(b_i\) 能被 \(B\) 中的面值表示出来,那么 \(b_i\)多余了。

综上,最优解中不存在 \(B\) 中的面额不属于 \(A\)

实现

我们知道了 \(B\) 中面额取自 \(A\),也就是说 \(B\)\(A\) 的子集。

那么思路已经很明显了,显然 \(A\) 中每个能被其他面额表示出来的面额都不是 \(B\) 中的面额。

所以只要找到 \(A\) 中每个能被其他面额表示出来的面额即可。

这里完全背包的思路就显现出来了,设 \(dp[i]\) 金额 \(i\) 能否被 \(A\) 中面额表示出来,若 \(dp[i]\)\(true\) 则能,\(false\) 则不能。

那么就可以将 \(A\) 先从小到大sort一遍,然后对于每个 \(a_i\) ,有:\(dp[a_i]=dp[a_i-a_1] \parallel dp[a_i-a_2] \parallel ... dp[a_i-a_{i-1}]\)

直接上代码:

code

//writer:Oier_szc

#include <bits/stdc++.h>
//#define int long long
using namespace std;
const int N=25010;
int t,n,a[N];
int dp[N];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;++i)
		{
			scanf("%d",&a[i]);
		}
		sort(a,a+n);
		//先sort一遍 
		memset(dp,0,sizeof(dp));
		dp[0]=true;
		//注意dp初始化 
		int ans=0;
		for(int i=0;i<n;++i)
		{
			if(!dp[a[i]]) ++ans;
			for(int j=a[i];j<=a[n-1];++j)
			{
				dp[j]|=dp[j-a[i]];
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2023-02-04 20:41  Oier_szc  阅读(80)  评论(0)    收藏  举报