刷题记录-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;}
一般来说,凭我的感觉。。。加权并查集和镜像并查集是可以互相转化的,比如说是食物链

********************************************************

总之,后来想了想,还是并查集更加合适,这题是训练并查集的一道经典题