https://www.luogu.com.cn/problem/P1525
思考:为什么匹配到仇恨值最大的朋友(敌人的敌人)输出的仇恨值最低?
答案:所谓朋友,就是分配在同一座监狱,所谓敌人,就是分配在不同的监狱如果是敌人,那么他们不属于同一个并查集
属于同一个并查集的数学意义:他们是朋友
所以,找朋友就是找两个元素根节点是否相同
既然他们是朋友了,也就是说由前置条件已经无法再分到不同的监狱了,即使这对朋友仇恨值最大,也没办法了,所以他们的仇恨值就是最小的最大仇恨值分配方案
从代码的角度:
if(Find(x) == Find(y)) {
printf("%d\n", a[i].c);
break;
}
如果 x和 y在同一座监狱,证明无法再继续优化分配了
因为前面矛盾值大的都已经优化分配了(优先把敌人分走)
那么此时这对的矛盾值就是 Z市长看到的最大矛盾值
直接输出并结束
从实例角度分析
一共两座监狱
若N=2,冲突0,若N=3,其中一座关一个人,则排序选择最小的即可
那么N=4是最简单的情况,我们由易至难来分析,下面分析四个N=4的案例
案例一
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
i C[i].a C[i].b C[i].w
0 1 2 28351
1 3 4 12884
2 1 3 6618
3 2 3 3512
4 1 4 2534
5 2 4 1805
1 2 28351
merge(1,6) x=1 y=6 rank[1]==1 rank[6]==1 fa[6]=1 rank[1]=2
merge(2,5) x=2 y=5 rank[2]==1 rank[5]==1 fa[5]=2 rank[2]=2
3 4 12884
merge(3,8) x=3 y=8 rank[3]==1 rank[8]==1 fa[8]=3 rank[3]=2
merge(4,7) x=4 y=7 rank[4]==1 rank[7]==1 fa[7]=4 rank[4]=2
1 3 6618
merge(1,7) x=1 y=4 rank[1]==2 rank[4]==2 fa[4]=1 rank[1]=3
merge(3,5) x=3 y=2 rank[3]==2 rank[2]==2 fa[2]=3 rank[3]=3
2 3 3512
fa[2]=3 fb[3]=3
3512
从所谓朋友,就是分配在同一座监狱,所谓敌人,就是分配在不同的监狱的角度思考,问题迎刃而解,思路无比清晰
如果冲突条件不够,则可以分配到无冲突的案例,如下例
4 2
1 2 10
3 4 15
我们把1 3关在一起,2 4关在一起,无冲突
案例二,N=6
N=6
6 11
1 2 4564
1 3 782
1 4 984
1 5 673
1 6 494
2 3 789
2 4 798
2 5 79421
3 6 897
4 5 9821
4 6 9784
merge(2,11) x=2 y=11 rank[2]==1 rank[11]==1 fa[11]=2 rank[2]=2
merge(5,8) x=5 y=8 rank[5]==1 rank[8]==1 fa[8]=5 rank[5]=2
merge(4,11) x=4 y=2 rank[4]==1 rank[2]==2 fa[4]=2
merge(5,10) x=5 y=10 rank[5]==2 rank[10]==1 fa[10]=5
merge(4,12) x=2 y=12 rank[2]==2 rank[12]==1 fa[12]=2
merge(6,10) x=6 y=5 rank[6]==1 rank[5]==2 fa[6]=5
merge(1,8) x=1 y=5 rank[1]==1 rank[5]==2 fa[1]=5
merge(2,7) x=2 y=7 rank[2]==2 rank[7]==1 fa[7]=2
merge(1,10) x=5 y=5 rank[5]==2 rank[5]==2 fa[5]=5
merge(4,7) x=2 y=2 rank[2]==2 rank[2]==2 fa[2]=2
merge(3,12) x=3 y=2 rank[3]==1 rank[2]==2 fa[3]=2
merge(6,9) x=5 y=9 rank[5]==2 rank[9]==1 fa[9]=5
fa[2]=2 fb[4]=2
798
i C[i].a C[i].b C[i].w
0 2 5 79421
1 4 5 9821
2 4 6 9784
3 1 2 4564
4 1 4 984
5 3 6 897
6 2 4 798(结束算法)
7 2 3 789
8 1 3 782
9 1 5 673
10 1 6 494
其实从i=3开始,监狱就分配完毕了,但是算法从第一对朋友(仇恨值最大的朋友)相遇才结束,输出他们的仇恨值即为最大仇恨值
由此观之,这道题用种类并查集并不是最快的算法,还可以更快
而这道题思路也有很多种,种类并查集仅仅是其中一种
测试用代码如下
#include <cstdio>
#include <cctype>
#include<iostream>
#include <algorithm>
using namespace std;
int read() //快速读入,可忽略
{
int ans = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
{
ans = (ans << 3) + (ans << 1) + c - '0';
c = getchar();
}
return ans;
}
struct data //以结构体方式保存便于排序
{
int a, b, w;
} C[100005];
int cmp(data &a, data &b)
{
return a.w > b.w;
}
int fa[40005], rank[40005]; //以下为并查集
int find(int a)
{
return (fa[a] == a) ? a : (fa[a] = find(fa[a]));
}
int query(int a, int b)
{
int fa=find(a);
int fb=find(b);
if(fa==fb){
cout<<"fa["<<a<<"]="<<fa<<" "<<"fb["<<b<<"]="<<fb<<endl;
}
return find(a) == find(b);
}
void merge(int a, int b)
{
cout<<"merge("<<a<<","<<b<<") "<<"x=";
int x = find(a), y = find(b);
cout<<x<<" y="<<y<<" rank["<<x<<"]=="<<rank[x]<<" rank["<<y<<"]=="<<rank[y]<<" ";
if (rank[x] >= rank[y]){
fa[y] = x;
cout<<"fa["<<y<<"]="<<x<<" ";
}
else{
fa[x] = y;
cout<<"fa["<<x<<"]="<<y<<" ";
}
if (rank[x] == rank[y] && x != y){
rank[x]++;
cout<<"rank["<<x<<"]="<<rank[x]<<endl;
}else cout<<endl;
}
void init(int n)
{
for (int i = 0; i <= n; ++i)
{
rank[i] = 1;
fa[i] = i;
}
}
int main()
{
int n = read(), m = read();
init(n * 2); //对于罪犯i,i+n为他的敌人
for (int i = 0; i < m; ++i)
{
C[i].a = read();
C[i].b = read();
C[i].w = read();
}
std::sort(C, C + m, cmp);
cout<<"i "<<"C[i].a "<<"C[i].b "<<"C[i].w "<<endl;
for (int i = 0; i < m; ++i)
cout<<i<<" "<<C[i].a<<" "<<C[i].b<<" "<<C[i].w<<" "<<endl;
for (int i = 0; i < m; ++i)
{
if (query(C[i].a, C[i].b)) //试图把两个已经被标记为“朋友”的人标记为“敌人”
{
printf("%d\n", C[i].w); //此时的怒气值就是最大怒气值的最小值
break;
}
merge(C[i].a, C[i].b + n);
merge(C[i].b, C[i].a + n);
if (i == m - 1) //如果循环结束仍无冲突,输出0 例如
puts("0");
}
return 0;
}
AC代码
//https://blog.csdn.net/qq_54886579/article/details/119423864
//https://www.luogu.com.cn/problem/P1525
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e4+1;
const int M=1e5+1;
int fa[N*2];//并查集,全称father
int rank[N*2];
int read(){//快读
char ch=getchar();
int s=0,w=1;
while(ch<'0' || ch>'9'){
if(ch=='-') w*=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){//不要写成|| !
s=ch-'0'+(s<<3)+(s<<1);
ch=getchar();//不要忘记!
}
return s*w;
}
struct node{//存放仇恨情况
int l,r,v;
}P[M];
bool cmp(node a,node b){//按仇恨值从大到小排序
return a.v>b.v;
}
void init(int n){//初始化并查集
for(int i=0;i<=n*2;i++){
fa[i]=i;
rank[i]=0;
}
}
int find(int x){//寻找根节点 非路径压缩
return (x==fa[x]) ? x : fa[x]=find(fa[x]);
}
bool judge(int a,int b){//判断a,b是否同属一个根节点
return find(a)==find(b);
}
void merge(int a,int b){//按秩合并
int Fa=find(a);
int Fb=find(b);
if(rank[Fa]>=rank[Fb]){//秩小的接秩大的上
fa[Fb]=Fa;
}else fa[Fa]=Fb;
if(rank[Fa]==rank[Fb] && Fa!=Fb) rank[Fa]++;
}
int main(){
int n=read(),m=read();
init(n*2);
for(int i=1;i<=m;i++){
P[i].l=read();
P[i].r=read();
P[i].v=read();
}
int flag=1;
sort(P+1,P+m,cmp);//易错点:sort从P+1开始!!否则就会排序P[0]到P[5],而P[0]是0 0 0
for(int i=1;i<=m;i++){
int l,r,v;
l=P[i].l,r=P[i].r,v=P[i].v;
if(judge(l,r)){//l与r是朋友
printf("%d",v);
flag=0;
break;
}else{
merge(l,r+n);
merge(r,l+n);
}
}
if(flag) printf("0");
return 0;
}