处理一类体积为实数的背包问题的技巧
作者:互联网
CF366C Dima and Salad (背包 一个 trick)
题目要求取满足 ∑ 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 )
观察到求满足 ∑ 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