刷题记录-luoguP1525 关押罪犯
来源:互联网 发布:应聘淘宝客服被骗 编辑:程序博客网 时间:2024/06/07 04:03
这题我采用的方法是二分答案,二分边的长度D,
注意左边界是0开始的,因为可能不会发生冲突。
然后难点就是check函数判断相应的长度是否可行,
我的思路是这样的:
如果可行的话,那么长度大于D的边两个节点,肯定会被分在两个集合里面,
从而想到了二分图:新建一个图,只保存原图中大于D的边,然后判断是否是二分图即可。
二分图判断可以用深搜染色,效率是O(|V|+|E|)
我用的是并查集,如果A和B要相连的话,那么连接A和B',以及B和A'。
把所有的边处理,然后假如A和A'相连的话,那么就一定不是二分图,
二分图的数学定义:而且仅当图中不存在奇环,如果A和A'相连的话,那么肯定存在奇环的。
详见代码:
*************************************
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cstring>
#define pii pair<int,int>
#define MAXE 100005
#define MAXV 20005
using namespace std;
struct edge{
int u,v;
int c;
};
int V,E;
int D;
int f[MAXV*2];
edge e[MAXE];
int find(int x){
return (f[x]==x)?x:(f[x]=find(f[x]));
}
void init_find(){
for(int i=1;i<=(V<<1);i++){
f[i]=i;
}
}
void lik(int x,int y){
x=find(x),y=find(y);
if(x!=y){
f[x]=y;
}
}
bool same(int x,int y){
return (find(x)==find(y));
}
int check(){
init_find();
int i;
for(i=E;i>=1;i--){
if(e[i].c<=D){
break;
}
lik(e[i].v,e[i].u+V);
lik(e[i].u,e[i].v+V);
}
for(int i=1;i<=V;i++){
if(same(i,i+V)){
return 0;
}
}
return 1;
}
bool comp(const edge &e1,const edge &e2){
return (e1.c<e2.c);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%d%d",&V,&E);
for(int i=1;i<=E;i++){
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c);
}
sort(e+1,e+E+1,comp);
// for(int i=1;i<=E;i++){
// printf("%d %d %d\n",e[i].u,e[i].v,e[i].c);
// }
int l=0,r=E;
while(l!=r){
int mid=l+(r-l)/2;
D=e[mid].c;
if(check()){
r=mid;
}
else{
l=mid+1;
}
}
printf("%d",e[r].c);
return 0;
}
**************************************************
我的算法复杂度是|E|log|E|,这里可以过,但是有更为简洁的并查集写法。
这些写法的共同点:
运用贪心的思想,进行从大到小地排序,很大地提高了效率。
主要有三种写法:
(1)朴素并查集
思路是:先排序边的权值,按从大到小的顺序处理,把每个节点所有的敌人用并查集连起来,形成“两大阵营”。
当发现边的两个节点在一个阵营里面时,那么冲突肯定不可避免了,而且此冲突值是最优解,因为是边的权值是从大到小的。
附上代码:
#include<cstdio>#include<cstdlib>#include<iostream>#include<algorithm>#include<cstring>#define MAXV 20005#define MAXE 100005using namespace std;struct edge{ int u,v,c;};edge e[MAXE];int V,E;int f[MAXV],b[MAXV];int find(int x){ return (f[x]==x)?x:(f[x]=find(f[x]));}void init_find(){ for(int i=1;i<=V*2;i++){ f[i]=i; }}void lik(int u,int v){ u=find(u),v=find(v); if(u!=v){ f[u]=v; }}bool same(int u,int v){ return (find(u)==find(v));}bool comp(const edge &e1,const edge &e2){ return (e1.c>e2.c);}int main(){// freopen("data.in","r",stdin); scanf("%d%d",&V,&E); init_find(); for(int i=1;i<=E;i++){ scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c); } sort(e+1,e+E+1,comp); for(int i=1;i<=E;i++){ int u=e[i].u,v=e[i].v,c=e[i].c; if(same(u,v)){ printf("%d\n",c); return 0; } if(!b[u]) b[u]=v; else lik(v,b[u]); if(!b[v]) b[v]=u; else lik(u,b[v]); } printf("0\n"); return 0;}
(2)加权并查集用加权的方式,记录节点到根的距离,那么可以推出:
如果两个节点连通,且到根的距离都为奇数,或者都为偶数,那么肯定是同一个监狱的,肯定会起冲突。
#include<cstdio>#include<cstdlib>#include<iostream>#include<algorithm>#include<cstring>#define MAXV 20005#define MAXE 100005using namespace std;struct edge{ int u,v,c;};edge e[MAXE];int V,E;int f[MAXV],w[MAXV];void init_find(){ for(int i=1;i<=V;i++){ f[i]=i; }}int find(int x){ if(x!=f[x]){ int t=f[x]; f[x]=find(f[x]); w[x]+=w[t]; //注意这里,根节点到自己的距离是0,所以可以加 } return f[x];}bool comp(const edge &e1,const edge &e2){ return (e1.c>e2.c);}int main(){// freopen("data.in","r",stdin); scanf("%d%d",&V,&E); init_find(); for(int i=1;i<=E;i++){ scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c); } sort(e+1,e+E+1,comp); for(int i=1;i<=E;i++){ int u=e[i].u,v=e[i].v,c=e[i].c; int x=find(u),y=find(v); if(x==y){ if((w[u]&1)==(w[v]&1)){ printf("%d\n",c); return 0; } } else{ f[x]=y; if((w[u]&1)==(w[v]&1)){ w[x]=1; //为了防止连接之后出现都为奇数或者都为偶数的情况,给将要连上的原先根节点权值1 } } } printf("0\n"); return 0;}
(3)镜像并查集如果v和u相连,那么令v与u+n相连,u与v+n相连。
表示的含义是v与u不在同一个阵营。
这种巧妙的方法恰好形成了两个部分,与题中两大阵营对应。
#include<cstdio>#include<cstdlib>#include<iostream>#include<algorithm>#include<cstring>#define MAXV 20005*2#define MAXE 100005using namespace std;struct edge{ int u,v,c;};edge e[MAXE];int V,E;int f[MAXV];int find(int x){ return (f[x]==x)?x:(f[x]=find(f[x]));}void init_find(){ for(int i=1;i<=V*2;i++){ f[i]=i; }}void lik(int u,int v){ u=find(u),v=find(v); if(u!=v){ f[u]=v; }}bool same(int u,int v){ return (find(u)==find(v));}bool comp(const edge &e1,const edge &e2){ return (e1.c>e2.c);}int main(){// freopen("data.in","r",stdin); scanf("%d%d",&V,&E); init_find(); for(int i=1;i<=E;i++){ scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].c); } sort(e+1,e+E+1,comp); for(int i=1;i<=E;i++){ int u=e[i].u,v=e[i].v,c=e[i].c; if(same(u,v)){ printf("%d\n",c); return 0; } lik(u,v+V); lik(v,u+V); } printf("0\n"); return 0;}
一般来说,凭我的感觉。。。加权并查集和镜像并查集是可以互相转化的,比如说是食物链********************************************************
总之,后来想了想,还是并查集更加合适,这题是训练并查集的一道经典题
- 刷题记录-luoguP1525 关押罪犯
- luoguP1525关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- 关押罪犯
- OI刷题记录
- OI刷题记录~
- leetcode刷题记录
- 刷题记录
- 6.22刷题记录
- 7.26-刷题记录
- HDU 5795 A Simple Nim (SG函数+打表找规律)
- vuex学习笔记
- 朱金付C++第五章
- (三)Redux:创建和使用Reducer
- 嵌入人生
- 刷题记录-luoguP1525 关押罪犯
- 小白学爬虫-----bs4的学习-1
- 【HNOI2016模拟4.4】Alphadog
- JS编程训练 | 题14:正确函数定义
- 单片机显示时钟
- (四)Redux:使用combineReducers函数
- poj 1006 生理周期 中国剩余定理
- 【PAT】【Advanced Level】1011. World Cup Betting (20)
- python -- 处理换行格式(os.popen('top -bn 1'))