【CCF Online3普及组】买表

【CCF Online3普及组】买表

(File IO): input:watch.in output:watch.out

题目描述

Jimmy 到 Symbol 的手表店买手表,Jimmy 只带了 n 种钱币,第 i种钱币的面额为 k_i元,张数为 a_i张。Symbol 的店里一共有 m块手表,第 i块手表的价格为 t_i 元。
Symbol 的手表店不能找零,所以 Jimmy 只能在凑出恰好的钱数时才能购买一块手表。现在对于店里的每块手表,Jimmy 想知道他能不能凑出恰好的钱数进行购买。

输入

第一行两个空格分隔的整数 n和 m表示钱币数与手表数。

接下来 n行每行两个空格分隔的整数 k_i,a_i表示钱币的面额和张数。

第 n+2行,共 m个用空格分隔的整数 t_i,表示每块手表的价格。

输出

一共 m行,对于第 i行,如果能凑出恰好的钱数购买第 i块手表则输出"Yes"(不含引号,下同),否则输出"No",注意只有首字母大写。

样例输入

3 5
1 2
5 1
6 3
3 19 21 1 7

样例输出

No
Yes
No
Yes
Yes

数据范围限制

对于 50%的数据,n≤10,m≤60,a_i≤20,k_i≤5000,t_i​≤250。

对于 100%的数据,1≤n≤200,1≤m≤10^5,1≤a_i​≤1000,1≤k_i​≤500000,0≤t_i≤500000。

提示

第二块手表 19=6×3+1=6×2+5+1×2,可以恰好凑出。

第四块手表 1=1×1,可以恰好凑出。

第五块手表 7=5+2×1=6×1+1,可以恰好凑出。

想当年,我五年级时,参加这个比赛,写了个暴搜,骗了个40分。这道题显然是一个动态规划的背包问题,但是如果写多重背包的话,那就只能拿50分,而这道题的正确写法应该是01背包+二进制优化。把面额同时当作重量和价值,再把多重背包转化成01背包,再加上二进制优化,再写一段动规,输出,就可以了。把多重背包转化成01背包大家应该都会,假如第一、二、三个数分别是重量、价值、数量,那么,例如:
3 4 4
可以转为
3 4 1
3 4 1
3 4 1
3 4 1
再把后面的1去掉,就变成01背包了。但这样还远远不够,我们还需要二进制优化。但是应该怎么优化呢?例如:
3 4 4
可以转为
3×1 4×1
3×2 4×2
3×1 4×1

3 4
6 8
3 4

再例如:
3 4 17
可以转为
3×1 4×1
3×2 4×2
3×4 4×4
3×8 4×8
3×2 4×2

3 4
6 8
12 16
24 32
6 8

为什么可以这样转呢?大家从中可以发现,经过拆分,我们依然可以组成数量为1 ~ 4(或14,在例子中)的情况,且不遗漏,也不会多算。是不是很神奇?观察第二个乘数,除了第一和最后一组,每一组都是前一组的两倍。大家知道,20+21+22+……+2n=2n+1-1,二进制优化就是根据这一个原理来做到不遗漏的(且让组数尽量小,来提高程序效率)。但是最后一组又是怎么回事?咱们来看看第二个例子。如果把第二个例子的最后一组删掉,我们就只能组成数量为1 ~ 15的情况。这时,还时还差2,才能组成数量为1~17的情况,这时,我们只需要加上一组3×2 4×2,就可以解决了。

#include
int nn,n,m,k[205],a[205],maxx,t[100005],kk[200005],dp[500005];
int max(int x,int y)
{
     
	return x>y?x:y;
}
int main()
{
     
	freopen("watch.in","r",stdin);
	freopen("watch.out","w",stdout);
	scanf("%d%d",&nn,&m);
	for(int i=1;i<=nn;++i)
	{
     
		scanf("%d%d",&k[i],&a[i]);
	}
	for(int i=1;i<=m;++i)
	{
     
		scanf("%d",&t[i]);
		if(t[i]>maxx)//求m个手表的最大价格
		{
     
			maxx=t[i];
		}
	}
	for(int i=1;i<=nn;++i)
	{
     
		int p=1;//二进制优化,p就是第二个乘数
		while(a[i]>=p)//判断是否还能拆分
		{
     
			if(p*k[i]>maxx) break;//如果面额大于手表的最大价值,那这张纸币就没用了
			n++;
			kk[n]=p*k[i];//把经过处理的数放到新的数组里,kk即表示“重量”,也表示“价值”(题目中不是重量和价值)
			a[i]-=p;//减上p,做到不多算
			p*=2;
		}
		if(a[i]>0&&a[i]*k[i]<=maxx)//处理最后一组
		{
     
			n++;
			kk[n]=a[i]*k[i];
		}
	}
	for(int i=1;i<=n;++i)//动规
	{
     
		for(int j=maxx;j>=kk[i];--j)
		{
     
			dp[j]=max(dp[j],dp[j-kk[i]]+kk[i]);
		}
	}
	for(int i=1;i<=m;++i)
	{
     
		if(dp[t[i]]==t[i])//如果可以组成
		{
     
			printf("Yes\n");
		}
		else
		{
     
			printf("No\n");
		}
	}
}

不见不散(新年好!!!)

你可能感兴趣的:(动态规划)