NOI Online #3 入门组 第三题:买表

NOI Online #3 入门组 第三题:买表

  • 题目
  • 解析
    • 暴力1
    • 暴力2
    • 正解
  • 代码

题目

题目传送门

解析

暴力1

首先,我先确定一下暴力是怎么做的。
我在考场上做的暴力是使用DFS所做的,时间复杂度高到要爆。
但是我这个蒟蒻还是用了。
最终得分50。

暴力2

可是我还想AC啊,怎么办呢,那么我就请教了dalao们,他们说:

我谔谔,你连dp+二进制都做不出来吗? \color{blue}\boxed{\color{red}\texttt{我谔谔,你连dp+二进制都做不出来吗?}} 我谔谔,你连dp+二进制都做不出来吗?

原来这道题是dp+二进制的方法,我先想出来了暴力的多重背包,它的时间复杂度是 O ( N × W × k ) O(N \times W \times k) O(N×W×k) W W W就是该题中 T i T_i Ti的最大值, k k k即为题中的 a i a_i ai。总时间复杂度是 200 × 1000 × 50000 = 1 0 10 200 \times 1000 \times 50000=10^{10} 200×1000×50000=1010,肯定是爆掉了。

那该怎么办呢?

正解

之前我们把多重背包拆成 a i a_i ai​ 个 01 背包,我们要优化这种拆分方法。我们的目的是表示 [ 1 , a i ] [1,a_i] [1,ai] 的所有数。逐个拆分显然可行,但二进制拆分更优。设 2 p ≤ a i ≤ 2 p + 1 2^p \le a_i \le 2^{p+1} 2pai2p+1,那么我们只需要拆出 2 0 , 2 1 , 2 2 … 2 p , a i − 2 p 2^0,2^1,2^2 \dots 2^p,a_i-2^p 20,21,222p,ai2p 这些数,也能表示 [ 1 , a i ] [1,a_i] [1,ai] 的所有数。那么,之前枚举 a i a_i ai​ 的循环现在只用枚举 [ 1 , p ] [1,p] [1,p],p 是 log ⁡ a i \log a_i logai 级别的。至此,总复杂度为 O ( N × W × log ⁡ a i ) O(N\times W\times \log a_i) O(N×W×logai),凭借背包优秀的常数,可以AC这道题!

代码

(DFS暴力解法以及暴力多重背包解法就不给了)

正解代码:

(PS:代码中有快读,如过您不知道的话,请看我的blog:C++快读)

#include
using namespace std;
#define LL long long
const int Maxm=500000;
int read() //快读
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
int n,m;
int dp[500000+5]; //dp
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
	{
		int w=read();
		int k=read();
		for(int j=0;j<10;j++) //拆分过程
		{
			k-=(1<<j);
			if(k>=0)
			{
				int Zhi=w*(1<<j); //不再一个个拆了,只拆成 2 的指数幂个
				for(int j=Maxm;j>=Zhi;j--)
				{
					dp[j]=max(dp[j],dp[j-Zhi]+Zhi);//0-1背包模板
				}
			}else{
				k+=(1<<j);
				break;
			}
		}
		int Zhi=w*k;
		for(int j=Maxm;j>=Zhi;j--)
		{
			dp[j]=max(dp[j],dp[j-Zhi]+Zhi);
		}
		//剩下的还要打个包
	}
	for(int i=1;i<=m;i++)
	{
		int x=read();
		if(dp[x]==x) puts("Yes");
		else puts("No");
	}
	return 0;
}

你可能感兴趣的:(C++,Noi的题目,C++题解)