背包题目小结(持续更新中。。。长春赛H题)

01背包
hdu 3466
给n个物品,m元钱,要买第i个物品,需拥有不小于q[i]的钱并花费p[i]的钱。这题是个与顺序有关的背包。对物品a,b,当前有钱m。要买物品a,b。要先买a在买b,需满足,m>=qa && m>=pa+qb,先买b再买a就要满足,m >= qb && m >= pb+qa. 我们先考虑pa+qb>=qa && pb+qa>=qb的情况,这样比较容易得出规律。要满足先买a再买b更优,则得qa-pa>=qb-pb.然后我们可以接着验证这个性质依然满足其他情况。所以买物品的要按qi-pi从大到小去买。而dp的时候,因为dp[i]取决的dp[i-j]的解。该按从小到大的顺去推。
hdu 2623
n个物品,每次用两台车运,给出每台车的转载能力和每个物品的重量,问最少几次运完。dp[i]第k辆车运集合i中物品最少次数,res[i]表示运集合i后车的最大剩余载重。利用这两个数组对两辆车分别dp,然后在暴力合并就好
hdu 2639
第k大背包问题
多开一维表示第k大时的价值可以理解为原来普通背包时dp[i][j]表示的是最优价值,现在表示第1到第k大价值。转移的时候为O(k)的归并。
poj 2184
背包变形
这题主要是要把其转换为背包模型。首先我们分析问题。要求在ts,tf大于等于0的情况下的ts+tf最大值。而背包问题是在总体积不大于背包情况下的最大价值,很容易联系起来。模仿写出dp[i][j]表示ts是i,tf是j情况下的最大值,然后易变为dp[i]表示ts=i 大于等于0时的tf最大值。这样就变成了一个普通背包。


完全背包 & 多重背包
poj 3260
这题本身不难,对硬币进行一次完全背包一次多重背包就好,但是从这题开始我改变一下背包问题的写法。
void zero_one_pack(int *F,int C,int W)
{
	for(int v = V; v >= C; v--)
		F[v] = min(F[v],F[v-C]+W);
}
void complete_pack(int *F,int C,int W)
{
	for(int v = C; v <= V; v++)
		F[v] = min(F[v],F[v-C]+W);
}
void multiple_pack(int *F,int C,int W,int M)
{
	if(C * M >= V){
		complete_pack(F,C,W);
		return ;
	}
	int k = 1;
	while(k < M){
		zero_one_pack(F,k*C,k*W);
		M -= k;
		k *= 2;
	}
	zero_one_pack(F,M*C,M*W);
}


zoj 3524
给一个有向图,告诉起点,终点随意,背包可承重W,第i个点有无限的物品i可买,价值v[i],重量w[i],从i到j的花费等于dis[i][j]乘上此时已买物品的总重。问最后最大价值和此时最小花费
dp[i][j]表示到第i个山峰时 总重 不大于j时的最大收益,pw[i][j]表示 总重 是j时的最小花费.
这题容易看出是完全背包,但是显然不能直接贴模板。
首先来个拓扑排序,然后都没个点,首先预处理出此点由其前趋点转移过来时的dp值,然后就可以对该点进行完全背包了。
预处理创造完全背包的条件,这是本题的关键。

刚开始wa了,原因是我只用dp[][W]更新结果了。之所以错是把dp[i][j]里的那个j和pw[i][j]里的那个j看成一样的了。

void update(ll & f1,ll f2,ll &p1,ll p2)
{
	if(f2 > f1) f1 = f2,p1 = p2;
	else if(f1 == f2) p1 = min(p1,p2);
}
ll solve()
{
	memset(flag,false,sizeof(flag));
	memset(dp,0,sizeof(dp));
	memset(pw[X],0,sizeof(pw[X]));
	flag[X] = true;
	ll mx = 0,ans = 0;
	for(int _= 0; _ < n; _++){
		int i = ret[_];	
		if(i != X) for(int j = 0; j <= V; j++) pw[i][j] = inf;
		for(size_t idx  = 0; idx  < pre[i].size(); idx++){
			int k = pre[i][idx].first;
			int dis = pre[i][idx].second;
			if(flag[k] == false) continue;
			flag[i] = true;
			for(int j = 0; j <= V; j++){
				update(dp[i][j],dp[k][j],pw[i][j],pw[k][j]+dis*j);
				update(mx,dp[i][j],ans,pw[i][j]);
			}
		}
		if(!flag[i]) continue;
		for(int j = c[i]; j <= V; j++){
			update(dp[i][j],dp[i][j-c[i]]+w[i],pw[i][j],pw[i][j-c[i]]);
			update(mx,dp[i][j],ans,pw[i][j]);
		}
	}
	return ans;
}
hdu 3033
每组物品至少选一件,本来我的思路是先分组背包出每组只选一件的dp值,然后在对整个01背包,但是发现这样很难避免重复。

其实较好的思路是这样。dp[i][j]表示第i组选择完后体积为j的最大收益,对每个物品,从前一组转移一次(dp[i-1][j]),然后在再在本组内01背包只要初始化为-inf,那么第i组内第一件物品在更新时必然会被选择。之后的物品,要么要么在前面本组已选物品的基础上更新,要么最为该体积下本组第一件物品更新。实现的时候非常简单,先枚举,每组物品,然后在枚举体积,要注意的事对该物品必须在本组进行01背包更新然后在最为第一件物品更新。不然会有重复选择。(会有c[i]为0的情况。。。)


hdu 3535

分组背包综合,最少选一种,最多选一种,任意选,贴代码。

void atleast_one(int k,int n,int V)
{
	for(int i = 0; i < n; i++){
		for(int v = V; v >= c[i]; v--)
			dp[k][v] = max(dp[k][v],dp[k][v-c[i]]+w[i]);	
		for(int v = V; v >= c[i]; v--)
			dp[k][v] = max(dp[k][v],dp[k-1][v-c[i]]+w[i]);
	}
}
void atmost_one(int k,int n,int V)
{
	for(int v = 0; v <= V; v++){
		dp[k][v] = dp[k-1][v];
		for(int i = 0; i < n; i++) if(v-c[i] >= 0)
			dp[k][v] = max(dp[k][v],dp[k-1][v-c[i]]+w[i]);
	}
}
void choose_any(int k,int n,int V)
{
	for(int v = 0; v <= V; v++)   //不选
		dp[k][v] = dp[k-1][v];
	for(int i = 0; i < n; i++){
		for(int v = V; v >= c[i]; v--)  //本组内01背包
			dp[k][v] = max(dp[k][v],dp[k][v-c[i]]+w[i]);	
		for(int v = V; v >= c[i]; v--)  //作为本组内第一个的转移
			dp[k][v] = max(dp[k][v],dp[k-1][v-c[i]]+w[i]);
	}
}

hdu 3810
给几个连通块,对每个连通块做01背包。难点在于V和W的范围非常大1e9,常规的方式用两个循环dp显然不行,这里我们用两个队列模拟(相当于滚动数组),然后还要剪枝
用优先队列,然后去掉无用状态,显然对于c1,w1,c2,w2如果w1 >= w2且c1 <= c2,则物品2是无用的。
下面是对第k个连通块01背包的代码

ll solve(int k)
{
	priority_queue<node> q1,q2;
	node cur;
	cur.c = 0,cur.w = 0;
	q1.push(cur);
	ll ans = inf;
	for(size_t i = 0; i < group[k].size(); i++){
		while(!q1.empty()){
			cur = q1.top();
			q1.pop();
			q2.push(cur);
			cur.c += group[k][i].c;
			cur.w += group[k][i].w;
			while(cur.c < 0 || cur.w < 0);
			q2.push(cur);
			if(cur.w >= M){
				ans = min(ans,cur.c);
				continue;
			}
			if(cur.c >= ans) continue;
			q2.push(cur);
		}
		ll pre = inf;
		while(!q2.empty()){
			cur = q2.top();
			q2.pop();
			if(cur.c >= pre) continue;
			q1.push(cur);
			pre = cur.c;
		}
	}
	return ans;
}
zoj3662 (长春赛H题)
k个数,和为N,最大公倍数为M,问满足这个的k1-kn的序列有几种。
首先求出M的所有因子。然后就看起来是个和背包差不多的东西。
可以用分组背包的思想。一共K组,每一组都是M的所有因子,每组选一个。
dp[i][j][k]表示选完第i组后,和为j,lcm为k的方法数。

这里注意用mod会tle的。。

for(int s = 0; s < K; s++){
	int cur = s & 1;
	int nxt = 1 - cur;
	for(int j = 0; j <= N; j++)
		for(int k = 0; k < nf; k++)
			dp[nxt][j][k] = 0;
	//用k2去更新状态j,k1
	for(int k2 = 0; k2 < nf; k2++)
		for(int j = 0; j + c[k2] <= N; j++)
			for(int k1 = 0; k1 < nf; k1++) if(dp[cur][j][k1]){
				int &t = dp[nxt][j+c[k2]][mp[lcm[c[k1]][c[k2]]]];
				t += dp[cur][j][mp[c[k1]]];
				if(t > mod) t -=mod;
			}
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值