首页 > 代码库 > 【ZOJ】3812 We Need Medicine

【ZOJ】3812 We Need Medicine

这道题就题意来说其实就是一道简单的记录路径的0,1背包,告诉你n个物品,每种物品只能取一次,再有q个询问,问你是否能在满足选出物品的w之和为m的情况下,满足t之和为s的情况,若可以则任意输出一种方案。

因此我们可以设计状态,dp[i][j][k]为前i个物品选出部分,当t之和为j时,w之和为k的情况能否满足,若存在方案则为1,不存在则为0。而状态的转移方程也是很简单。

dp[i][j+t[i]][k+w[i]] = dp[i-1][j][k]。但是这样会超时,超时的原因是因为大量进行了重复的动作(即k的枚举)。因此可以用二进制来表示在当前j下可以表示的k的状态,然后通过位运算来快速进行所有k的推导,使得整个维度降低了一维,这样再记录路径就可以做了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int SIZEN = 200505;
const int M = 55;
int w[1005],t[1005];
LL dp[SIZEN];
LL has[1005];
int pre[SIZEN][55];
int ans[1000];
LL lowbit(LL x){
	return x & (-x);
}
void solve(){
	int n,q;
	scanf("%d%d",&n,&q);
	for(int i = 0 ; i < n ; i ++)
		scanf("%d%d",&w[i],&t[i]);
	memset(dp,0,sizeof(dp));
	dp[0] = 1;
	for(int i = 0 ; i < n ; i ++){
		for(int j = SIZEN - 5; j >= t[i] ;j --){
			LL tmp = dp[j];
			dp[j] |= (dp[ j - t[i] ]<<w[i]) & ((1ll<<(M + 1)) - 1);
			for(LL k = tmp ^ dp[j] ; k ; k -= lowbit(k)){
				LL tt = lower_bound(has,has+M,lowbit(k)) - has;
				pre[j][tt] = i;
			}
		}
	}
	int m,s;
	for(int i = 0 ; i < q ; i ++){
		scanf("%d%d",&m,&s);
		if(!(dp[s] & (1ll<<m))) printf("No solution!\n");
		else{
			int cnt = 0;
			while(s){
				int id = pre[s][m];
				ans[cnt++] = id + 1;
				s -= t[id];
				m -= w[id];
			}
			for(int i = 0 ; i < cnt - 1; i++) printf("%d ",ans[i]);
			printf("%d\n",ans[cnt - 1]);
		}
	}
}
int main()
{
	int _;
	scanf("%d",&_);
	for(int i = 0 ; i < M ; i++) has[i] = (1ll<<i);
	while(_--) solve();
}


【ZOJ】3812 We Need Medicine