P1525 [NOIP2010 提高组] 关押罪犯 种类并查集 思路剖析

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值