[NOIP2018]货币系统
做题时间:2022.7.20
\(【题目描述】\)
给定 \(T(T\leq 20)\) 组大小为 \(N(N\leq 100)\) 的自然数集合 \(A\) (其中的数大小不超过 \(25000\) ),求出最小的 \(M\) ,使得存在大小为 \(M\) 的自然数集合 \(B\) 满足任意一个自然数要么都能分别被 \(A\) 与 \(B\) 中的数凑出,要么都不能被凑出。
\(【输入格式】\)
第一行一个整数 \(T\)
每组数据第一行一个整数 \(N\)
每组数据第二行 \(N\) 个整数表示 \(A\)
\(【输出格式】\)
共 \(T\) 行,每行一个整数表示这组数据的答案
\(【考点】\)
背包DP
\(【做法】\)
对于这种多个数字拼凑起来,并且爆搜会挂的题目一般使用背包DP做。
首先肯定想到要从小到大便利 \(A\) ,因为大的数没办法凑出小的数,按这个顺序遍历利于统计答案。
可以想到用 \(f_{i,j}\) 表示用 \(A\) 中前 \(i\) 个数能否凑出 \(j\) ,直接跑一遍DP,转移方程:
\[f_{i,j}=f_{i-1,j-a_i}|f_{i-1,j-2\times a_i}\cdots
\]
这个方程其实很像完全背包的转移,回忆完全背包的优化可以发现, 其实不需要枚举 \(a_i\) 的数量,转移方程就变成:
\[f_{i,j}=f_{i-1,j-a_i}
\]
然后加上滚动数组即可。求解答案的时候只要在从小到大便利 \(a_i\) 的过程中判断当前的 \(a_i\) 是否已经被表示即可。
代码:
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e3+50,M=2e4+5e3+50;
bool f[M];
int n,t,maxn,a[N];
inline int Max(int a,int b){return a>b?a:b;}
int main()
{
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
maxn=Max(maxn,a[i]);
}
sort(a+1,a+1+n);
int cnt=n;
f[0]=true;
for(int i=1;i<=n;i++){
if(f[a[i]]){
cnt--;
continue;
}
for(int j=a[i];j<=maxn;j++) f[j]|=f[j-a[i]];
}
memset(f,false,sizeof f);
printf("%d\n",cnt);
}
return 0;
}

浙公网安备 33010602011771号