CodeForces - 742D Arpa's weak amphitheater and Mehrdad's valuable Hoses
题意:n个人通过朋友关系分为若干组,每个人都有重量和美丽值,选取人在舞台承重量为W上跳舞,在不超过W的情况下求出最大美丽值,在每一个组里,要么挑选一个人去跳舞,要么挑选所有人都去跳舞,要么都不挑选
思路:分组背包模板题,分组背包算法如下(摘自背包九讲)
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
算法
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}
使用一维数组的伪代码如下:
for 所有的组k
for v=V..0
for 所有的i属于组k
f[v]=max{f[v],f[v-c[i]]+w[i]}
注意这里的三层循环的顺序,甚至在本文的beta版中我自己都写错了。“for v=V..0”这一层循环必须在“for 所有的i属于组k”之外。这样才能保证每一组内的物品最多只有一个会被添加到背包中。
另外,显然可以对每组内的物品应用P02中“一个简单有效的优化”。
小结
分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(例如P07),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。
利用并查集求出总共用多少组,再把每一组的所有人重量之和,美丽值之和当作一个' 泛化物品 '添加到这组里
#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long ll;
int f[1001],w[1001],b[1001];
int len[1001];
ll dp[100005];
struct hose{
int w;
ll b;
}h[1001][1001];
int find(int x)
{
if(x==f[x]) return x;
else{
f[x]=find(f[x]);
return f[x];
}
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy) f[fy]=fx;
}
int main(void)
{
int n,m,V;
int x,y;
int k=0,kcnt;
ll sumw,sumb;
scanf("%d%d%d",&n,&m,&V);
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
f[i]=i;
}
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
merge(x,y);
}
for(int i=1;i<=n;i++){
if(f[i]==i){ //找到新的一组
sumw=0,sumb=0; //泛化物品,在这一组里所有人的重量之和,美丽值之和
k++; //组数加一
kcnt=1; //每一组有多少人
h[k][kcnt].w=w[i];
h[k][kcnt].b=b[i];
sumw+=w[i],sumb+=b[i];
}
for(int j=1;j<=n;j++)
{
if(j!=i&&find(j)==i){
kcnt++;
h[k][kcnt].w=w[j];
h[k][kcnt].b=b[j];
sumw+=w[j],sumb+=b[j];
}
}
//最后把泛化物品添加到这组里
kcnt++;
h[k][kcnt].w=sumw;
h[k][kcnt].b=sumb;
len[k]=kcnt;
}
//分组背包模板
for(int i=1;i<=k;i++)
for(int j=V;j>=0;j--)
for(int p=1;p<=len[i];p++){
if(j>=h[i][p].w)
dp[j]=max(dp[j],dp[j-h[i][p].w]+h[i][p].b);
}
printf("%lld\n",dp[V]);
return 0;
}