做题笔记: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;
}

浙公网安备 33010602011771号