西安邀请赛-D(带权并查集+背包)

时间:2021-03-10 20:49:57

题目链接:https://nanti.jisuanke.com/t/39271

题意:给定n个物品,m组限制,每个物品有个伤害值,现在让两个人取完所有物品,要使得两个人取得物品伤害值之和最接近,输出伤害值不小于另一个的人的伤害值,每组限制包括两次数x y,表示物品x和物品y不能由同一个人取得。

思路:思路是通过bfs或并查集将有关系的物品合并为一个物品,合并的物品有两个值,每个人必须分别取每个物品的一个值,然后就是背包问题了。

   具体实现就是用root[i]表示i的祖先,a[i]表示i与其祖先的关系(为0表示和祖先放在一边,为1表示和祖先不能放一边),可以得到(x->z=(x->y)^(y->z),x->z表示物品x,z的关系,还有x->y=y->x),然后就可以通过并查集来维护所有限制关系。之后遍历所有祖先等于自己的个数,即合并后的连通块个数,设为k。然后把第i个集合中和与祖先关系为0的都加在b[i][0]上,把第i个集合中和祖先关系为1的都加在b[i][1]上。然后还有个操作就是因为每个人分别取b[i][0],b[i][1]中的一个,要使差值最小,那么可以将b[i][0],b[i][1]同时减一个数使得min(b[i][0],b[i][1])=0,这并不影响答案。这样就是减去min(b[i][0],b[i][1])=0,剩下那个非0的数存进d[i]中,并且将所有d[i]加起来得到sum1。

   那么问题就转换为有k个物体,价值为d[i],这里价值和体积一样,在空间为sum/2的背包中,每次有选或不选两种可能,求背包最大的价值为多少。然后就可以得到结果了。说的有点绕,但思路不复杂,理解一下自己写写,代码写得有点乱,不建议看。。

AC代码:

#include<cstdio>
#include<map>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

int T,n,m,sum1,sum2,sum3,sum4,c[205],root[205],a[205];
int b[205][2],d[205],dp[50005];

int getr(int k){
    if(root[k]==k) return k;
    else{
        int tmp=root[k];
        root[k]=getr(root[k]);
        a[k]^=a[tmp];
        return root[k];
    }
}

int main(){
    scanf("%d",&T);
    while(T--){
        memset(b,0,sizeof(b));
        memset(d,0,sizeof(d));
        memset(dp,0,sizeof(dp));
        sum1=0,sum2=0,sum3=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)
            root[i]=i,a[i]=0;
        for(int i=1;i<=n;++i)
            scanf("%d",&c[i]),c[i]/=100,sum1+=c[i];
        while(m--){
            int x,y,rx,ry;
            scanf("%d%d",&x,&y);
            rx=getr(x),ry=getr(y);
            if(rx==ry) continue;
            root[ry]=rx;
            a[ry]=1^a[y]^a[x];
        }
        map<int,int> mp;
        int k=0;
        for(int i=1;i<=n;++i)
            if(getr(i)==i)
                mp[i]=++k;
        for(int i=1;i<=n;++i){
            int r=getr(i);
            b[mp[r]][a[i]]+=c[i];
        }
        for(int i=1;i<=k;++i)
            d[i]=abs(b[i][0]-b[i][1]),sum2+=d[i],sum3+=min(b[i][0],b[i][1]);
        sum4=sum2/2;
        for(int i=1;i<=k;++i)
            for(int j=sum4;j>=d[i];--j)
                dp[j]=max(dp[j],dp[j-d[i]]+d[i]);
        printf("%d\n",(sum1-(sum3+dp[sum4]))*100);
    }
    return 0;
}