[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;
}
posted @ 2022-07-21 11:15  lxzy  阅读(61)  评论(0)    收藏  举报