NOIp 2010/Luogu P1525 关押罪犯 【二分图/并查集】 By cellur925
感想:相信自己的想法!继续挖掘!
读完题目后:看到的最大值最小?二分答案啊!再仔细一看:wi达到了1e9,二分可能费点劲。(其实真的是可以的)而且check函数貌似并没有什么行之有效的写法。继续往下想。
再读读,想到我们肯定尽量不想让有仇恨的犯人关在一起,所以每次就把有仇的敌人用并查集并在一起(其实想法是挺好的,到这一步出现了偏差)。
结果...然后就没有结果了!
唉其实应该自己再多想想的嘛...还是去看了@Chemist和@new2zy两位巨佬的题解。主要有两种方法。
法一:二分图+二分答案
这题很好的满足了二分图的性质。是不错的二分图例题。
给出二分图定义:若无向图的n个节点可分为A,B两个非空集合,且A,B的交集为空集,且同一集合内的点没有边相连,称这种图为二分图。
二分图判定定理:一张无向图是二分图,当且仅当它不存在奇环。
代码实现:染色法。用黑白来标记图中两种节点,用大法师(Dfs)或绑发饰(Bfs)实现,遍历每条边如果没被访问,就染相反的颜色,否则判断它已染的色是否不合法。(根据定义,一个节点被标记后,它所有的相邻节点颜色应与他不同)。
get了这个知识,我们可以与最初的想法二分答案结合,本题中一个合法解需满足存在二分图的情况,而最优解可以通过二分实现。那么判断是否为二分图就成为了check函数的内核。
Code
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<queue> 5 6 using namespace std; 7 8 int n,m,tot,l,r; 9 int head[20090],vis[20090]; 10 struct node{ 11 int to,next,val; 12 }edge[200090]; 13 14 void add(int x,int y,int z) 15 { 16 edge[++tot].to=y; 17 edge[tot].val=z; 18 edge[tot].next=head[x]; 19 head[x]=tot; 20 } 21 22 bool check(int w) 23 { 24 memset(vis,0,sizeof(vis)); 25 queue<int>q; 26 for(int k=1;k<=n;k++) 27 if(!vis[k]) 28 { 29 q.push(k);vis[k]=1; 30 while(!q.empty()) 31 { 32 int x=q.front(); 33 q.pop(); 34 for(int i=head[x];i;i=edge[i].next) 35 if(edge[i].val>=w) 36 { 37 int y=edge[i].to; 38 if(!vis[y]) vis[y]=3-vis[x],q.push(y); 39 else if(vis[y]==vis[x]) return false; 40 } 41 } 42 } 43 return true; 44 } 45 46 int main() 47 { 48 scanf("%d%d",&n,&m); 49 // l=1; 50 for(int i=1;i<=m;i++) 51 { 52 int a=0,b=0,c=0; 53 scanf("%d%d%d",&a,&b,&c); 54 add(a,b,c); 55 add(b,a,c); 56 r=max(r,c); 57 }//r++; 58 while(l<r) 59 { 60 int mid=(l+r+1)>>1; 61 if(check(mid)) r=mid-1; 62 else l=mid; 63 } 64 printf("%d",l); 65 return 0; 66 }
*代码细节注意:入队地点(没被访问过),二分细节。
法二:冰茶几+略微贪心思想
由于Chemist大神已经讲的十分清楚,我就少发表些拙见。(扔下地址跑)
我们当然可以贪心地把仇恨值从大到小排序,先使仇恨值大的几对罪犯分到两个监狱,再秉承《团伙》一题中“敌人的敌人就是朋友”的观念,如果实在分不开了,当前的就是答案。
现在这么优质的一题多解的noip题已经很难找了。好题!好题!
独立意志与自由思想是必须争的,且须以生死力争。