其他分享
首页 > 其他分享> > 处理一类体积为实数的背包问题的技巧

处理一类体积为实数的背包问题的技巧

作者:互联网

CF366C Dima and Salad (背包 一个 trick)

CF366C Dima and Salad

题目要求取满足 ∑ a j ∑ b j = k \dfrac{\sum a_j}{\sum b_j}=k ∑bj​∑aj​​=k 时, ∑ a j \sum a_j ∑aj​ 的最大值,讨论如何表达背包体积,有如下技巧:

将原式移项,变式得到 ∑ ( a j − k b j ) = 0 \sum(a_j-kb_j)=0 ∑(aj​−kbj​)=0 于是发现,我们可以将 a j − k b j a_j-kb_j aj​−kbj​ 看作背包代价或体积,最后答案是 f ( 0 ) f(0) f(0) ,对于存在负数的问题,增添一个偏移量 Δ \Delta Δ 即可。

for(int i = 1;i <= n;i ++)
{
	cost[i] = a[i] - k*b[i];
	if(cost[i] > 0) maxn += cost[i];
	else minn += cost[i];
}
minn += d;maxn += d;
for(int i = 1;i <= n;i ++)
{
    if(cost[i] > 0)//注意此处体积/代价的符号对 01 背包顺序的影响
	{
		for(int j = maxn;j >= minn;j --)
			if(f[j] != -1)
				f[j + cost[i]] = max(f[j + cost[i]],f[j] + a[i]);
	}
	else
	{
		for(int j = minn;j <= maxn;j ++)
			if(f[j] != -1)
				f[j + cost[i]] = max(f[j + cost[i]],f[j] + a[i]);
	}
	f[cost[i] + d] = max(f[cost[i] + d],a[i]);
}
printf("%d",f[d]);

CF788C The Great Mixing ( 背包 同样的 trick )

CF788C The Great Mixing

观察到求满足 ∑ m a i ∑ m 1000 = n 1000 \dfrac{\sum^m a_i}{\sum^m 1000}=\dfrac{n}{1000} ∑m1000∑mai​​=1000n​ 的情况下,令 m m m 最小,显然,运用上面的 trick 我们又可以将其转化为将 a i − n a_i-n ai​−n 视为代价的背包问题,为了减少枚举量可以用队列储存可以被更新的点。对于枚举的边界问题,观察到题目的数据范围 a i ⩽ 1000 , n ⩽ 1000 a_i\leqslant 1000,n\leqslant 1000 ai​⩽1000,n⩽1000 ,由于我们的最终目标是将 ∑ ( a i − n ) = 0 \sum(a_i-n) = 0 ∑(ai​−n)=0 ,那么当当前的 ∑ ( a i − n ) \sum (a_i-n) ∑(ai​−n) 取到 − 1000 -1000 −1000 的时候,再取更小就没有意义了, a i a_i ai​ 最大就 1000 1000 1000 ,明明可能可以在下一步一次将代价总和加到 0 ,再取更小然后才加回来一定不是最优方案,于是可以知道,枚举范围为 [ − 1000 , 1000 ] [-1000,1000] [−1000,1000] 即可,为了保险,可以适当拓宽范围,只要不要令区间范围太大,比如 1 0 6 10^6 106 ,控制好区间范围即可。

int main()
{
	scanf("%d%d",&n,&k);
	for(int i = 0;i <= 5000;i ++) f[i] = inf;
	for(int i = 1;i <= k;i ++)
	{
		scanf("%d",&x);
		if(!vis[x - n + d])
		{
			f[x - n + d] = 1;vis[x - n + d] = 1;
			tot ++;a[tot] = x - n;
			q.push(x - n + d);
		}
	}
	for(int i = 0;i <= 5000;i ++) vis[i] = 0;
	for(;!q.empty();)
	{
		u = q.front();
		q.pop();
		for(int i = 1;i <= tot;i ++)
			if(a[i] + u >= 5 && a[i] + u <= 5000)
			{
				f[a[i] + u] = min(f[a[i] + u],f[u] + 1);
				if(!vis[a[i] + u])
				{
					vis[a[i] + u] = 1;
					q.push(a[i] + u);
				}
			}
	}
	if(f[d] == inf) printf("-1");
	else printf("%d",f[d]);
	return 0;
}

ARC060C Tak and Cards

AtCoder Regular Contest 060 C - Tak and Cards
求选择一些卡片使得的它们上面的整数的平均数恰好为 A A A 的方案数。
问题显然转化为求 ∑ m a i ∑ m 1 = A \dfrac{\sum^m a_i}{\sum^m 1}=A ∑m1∑mai​​=A 的方案数,同样运用 trick ,变式为求 ∑ ( a i − A ) = 0 \sum(a_i - A) = 0 ∑(ai​−A)=0 的方案数,于是背包即可。

#include<iostream>
#include<cstdio>

using namespace std;
typedef long long ll;
const int d = 3000;
int N,A,a[105];
ll f[6005];

int main()
{
	scanf("%d%d",&N,&A);
	for(int i = 1;i <= N;i ++)
	{
		scanf("%d",&a[i]);
	}
	f[a[1] - A + d] = 1;
	for(int i = 2;i <= N;i ++)
	{
		if(a[i] - A > 0)
		{
			for(int j = 6000;j >= 0;j --)
				if(f[j] > 0)
					f[j + a[i] - A] += f[j];
		}
		else
		{
			for(int j = 0;j <= 6000;j ++)
				if(f[j] > 0)
					f[j + a[i] - A] += f[j];
		}
		f[a[i] - A + d] ++;
	}
	printf("%lld",f[d]);
	return 0;
}

标签:背包,实数,sum,int,ai,cost,体积,1000
来源: https://blog.csdn.net/bell041030/article/details/121340266