https://ac.nowcoder.com/acm/contest/9984/F
将每个人看成一个点,武器的能力值抽象成边,这样就转化成图论的模型了。
然后可以转化成联通块去看
这种点之间连边的最值维护似乎跟并查集维护联通块容易联系到一起(应该是个trickC. Bertown Subway(思维+贪心+并查集维护联通块)
从联通块角度去看,如果一个联通块还没有形成环,那么他们之间的武器是可以互相换的,即他们当中的任意点都可以拥有新武器,我们称这个联通块没有饱和。反之,如果联通块内有环,那么这个块中的点都不能再拥有别的武器了我们称这个联通块饱和。
接下来我们分类讨论一下所有询问的情况:
- 两个点属于同一个联通块,如果这个联通块是饱和的,就略过;如果这个联通块不饱和,就加上当前武器的能力值并把联通块设置成饱和。
- 两个点属于不同的联通块,如果其中一个点是饱和的,那就将合并后的联通块设成饱和;如果都不是饱和,合并后也是不饱和;如果两个都是饱和的,就略过。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
LL fa[maxn],flag[maxn];
LL find(LL x){
if(fa[x]!=x) return fa[x]=find(fa[x]);
return fa[x];
}
struct P{
LL x,y,val;
}p[maxn];
bool cmp(P A,P B){
return A.val>B.val;
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;cin>>n>>m;
for(LL i=1;i<=n;i++) fa[i]=i;
for(LL i=1;i<=m;i++){
LL num;cin>>num;
if(num==1) cin>>p[i].x,p[i].y=p[i].x,cin>>p[i].val;
if(num==2) cin>>p[i].x>>p[i].y>>p[i].val;
}
sort(p+1,p+1+m,cmp);
LL ans=0;
for(LL i=1;i<=m;i++){
LL x=find(p[i].x);LL y=find(p[i].y);
if(x==y){
if(flag[x]) continue;
else{
ans+=p[i].val;
flag[x]=1;
}
}
else{
if(flag[x]&&flag[y]) continue;
else if(!flag[x]&&!flag[y]){
ans+=p[i].val;
fa[x]=y;
}
else{
ans+=p[i].val;
fa[x]=y;
flag[y]=1;
}
}
}
cout<<ans<<"\n";
return 0;
}
另外想谈一下这个匈牙利解法。
匈牙利的话就把装备看成左部点,每个最多两条边。然后贪心先取大的。
参考:F 魏迟燕的自走棋
每件装备不管配给谁,只要被配备,它的属性值就会对答案有贡献。
所以我们采取贪心的策略,按照属性值的大小从大到小地枚举装备,去找增广路。
若找到则把这件装备的属性值加到答案上。
若找不到则意味着一旦装上这件装备则会有另一件已经使用的装备被卸下来。而我们是按照属性值从大到小枚举的,所以被卸下来的装备的属性值一定不比装上的属性值小,所以总的来看这样做会减小总属性值。所以直接放弃即可。
增广路采用匈牙利算法寻找。
值得注意的是一般的匈牙利算法会超时,原因在于每次寻找增广路前都要清空 vis 数组,这会占用大量的时间。所以我们寻找完增广路后回溯地清空增广路上的 vis 数组,这样就不用额外清空了,节省了大量的时间。
至于没有找到增广路的点,意味着下一次遍历到它依然找不到增广路,所以他们的 vis 数组不清空也不影响答案。
https://ac.nowcoder.com/profile/94829516
注意,该方法只在左子图任意点的边数都不超过两条的情况适用,否则就必须对vis进行清空。以下是有一组hack样例(结果为4,但不清空vis可能导致结果为3)可自行推理,
4 4 8
1 2
1 3
2 3
2 2
2 1
3 3
3 4
4 2
不过我不是很懂这个复杂度是否真的能过这个1e5
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+100;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
bool vis[maxn];
LL match[maxn];
pair<LL,LL>p[maxn];
vector<LL>g[maxn];
bool dfs(LL u){
for(LL i=0;i<g[u].size();i++){
LL v=g[u][i];
if(vis[v]) continue;
vis[v]=true;
if(match[v]==0||dfs(match[v])){
match[v]=u;
vis[v]=false;
return true;
}
}
return false;
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
LL n,m;cin>>n>>m;
for(LL i=1;i<=m;i++){
LL num;cin>>num;
if(num==1) {LL x;cin>>x;g[i].push_back(x);LL val;cin>>val;p[i].first=val,p[i].second=i;};
if(num==2) {LL x,y;cin>>x>>y;g[i].push_back(x);g[i].push_back(y);LL val;cin>>val;p[i].first=val;p[i].second=i;}
}
sort(p+1,p+1+m);
LL ans=0;
for(LL i=m;i>=0;i--){
if(dfs(p[i].second)) ans+=p[i].first;
}
cout<<ans<<"\n";
return 0;
}