题目
解析
暴力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} 2p≤ai≤2p+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,22…2p,ai−2p 这些数,也能表示 [ 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<bits/stdc++.h>
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;
}