魏迟燕的自走棋 题解(并查集+思维)
题目链接
题目大意
有\(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;
}
不摆烂了,写题