魏迟燕的自走棋 题解(并查集+思维)

题目链接

题目大意

\(n(n \leq 1e5)\)个人,\(m(m\leq 1e5)\)件装备

每件装备可以给1个或2个人,而且每件装备有一个战力值,获得这个装备的人可以获得此战力值

要求每个人只能装备一件装备,每件装备只能分配给一个人。

求总战力值最大

思路

官方题解:

首先我们考虑把 m件装备看作 m 条连接两个点的边(如果 k=1 则当成自环)。于是我们的问题转化为选出一张边权和最大的生成子图。

考虑什么样的生成子图是合法的。我们要求选中的边能够和点一一配对,显然每个联通块的边数不能超过点数。同时又因为每个联通块都至少有点数-1条边,因此每个联通快要么是树,要么是基环树。

一个结论是每次都选最大边一定不劣。

一旦我们选择一条最大边后,我们把两个点缩成一个点(选中非自环的情况),或者直接把这个点丢掉(选中自环的情况),就面临一个规模更小且完全一致的问题,可以继续执行选取最大边的策略。

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int maxn=1e5+5,inf=0x3f3f3f3f;
//int mod=1e9+7;
const int eps=1e-3;
int n,m;
int p[3];
int fa[maxn];
bool flag[maxn];
struct edge{
    int u,v,w;
}e[maxn];
bool cmp(edge a,edge b){
    return a.w>b.w;
}
int findd(int x){
    return x==fa[x]?x:fa[x]=findd(fa[x]);
}
signed main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        fa[i]=i;
    }
    for(int i=1,num,val;i<=m;i++){
        scanf("%d",&num);
        for(int i=1;i<=num;i++){
            scanf("%d",&p[i]);
        }
        scanf("%d",&val);
        if(num==1) p[2]=p[1];
        e[i]={p[1],p[2],val};
    }
    sort(e+1,e+1+m,cmp);
    ll ans=0;
    for(int i=1;i<=m;i++){
        int x=findd(e[i].v),y=findd(e[i].u);
        if(x!=y){
            if(!flag[x]){
                flag[x]=1;
                fa[x]=y;
                ans+=e[i].w;
            }else if(!flag[y]){
                flag[x]=1;
                fa[y]=x;
                ans+=e[i].w;
            }
        }else{
            if(!flag[x]){
                flag[x]=1;
                ans+=e[i].w;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

不摆烂了,写题
原文地址:https://www.cnblogs.com/hunxuewangzi/p/14424130.html