NOIP2010 Codevs 1069 关押罪犯 [并查集] [二分图判定]

7 篇文章 0 订阅
7 篇文章 0 订阅

1069 关押罪犯 2010年NOIP全国联赛提高组
时间限制: 1 s
空间限制: 128000 KB
题目等级 : 钻石 Diamond

题目描述 Description
S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。

在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是少?

输入描述 Input Description
第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。

接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证,且每对罪犯组合只出现一次。

输出描述 Output Description
共1 行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。

样例输入 Sample Input
4 6

1 4 2534

2 3 3512

1 2 28351

1 3 6618

2 4 1805

3 4 12884

样例输出 Sample Output
3512

数据范围及提示 Data Size & Hint
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是3512(由2 号和3 号罪犯引发)。其他任何分法都不会比这个分法更优。

【数据范围】

对于30%的数据有N≤ 15。

对于70%的数据有N≤ 2000,M≤ 50000。

对于100%的数据有N≤ 20000,M≤ 100000。


这道题两种做法。

第一种用并查集,把点拆成两个,然后将边从大到小排序之后,一条条向并查集里面加,发现冲突即使答案,因为排过序,所以必须满足前面的不会冲突。
由于每次查询冲突的时候都是查询的真身,那么就需要每次用分身去更新。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
const int INF=0x3f3f3f3f;
const int maxn = 20005;
const int maxm = 100005;
struct Edge
{
    int u,v;
    int c;
    bool operator < (const Edge t) const
    {
        return c >= t.c;
    }
}edge[maxm];
int n,m;
int fa[maxn<<1];
int find(int x) { return x^fa[x]? fa[x]=find(fa[x]) : x; }
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n<<1;i++) fa[i]=i;
    for(int i=1;i<=m;i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c);
    sort(edge+1,edge+m+1);
    for(int i=1;i<=m;i++)
    {
        int x = find(edge[i].u) , y = find(edge[i].v);
        if(x==y)
        {
            printf("%d",edge[i].c);
            return 0;
        }
        fa[x] = find(edge[i].v + n);
        fa[y] = find(edge[i].u + n);
    }
    printf("0");
    return 0;
}

第二种做法使用二分图判定和二分答案。。
边从小到大排序之后二分是在哪条边发生最大冲突,那么当确定一条最大冲突的边的时候,需要对边权大于这条边的子图进行二分图判定,以判断是否还存在更大的冲突。。。
注意每次要从每个点尝试出发,因为可能不止一个连通分量。。
并且要在原图不存在冲突的时候输出0而不是最小边权。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
const int INF=0x3f3f3f3f;
const int maxn = 50005;
const int maxm = 100005;
struct Edge
{
    int to,next;
}edge[maxm<<1];
int head[maxn];
int maxedge;
inline void addedge(int u,int v)
{
    edge[++maxedge] = (Edge) { v,head[u] };
    head[u] = maxedge;
    edge[++maxedge] = (Edge) { u,head[v] };
    head[v] = maxedge;
}
struct Road
{
    int u,v;
    int c;
    bool operator < (const Road t) const
    {
        return c < t.c;
    }
}road[maxm];
int n,m;
int color[maxn];
bool bipartite(int u)
{
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v = edge[i].to;
        if(color[u] == color[v]) return false;
        if(!color[v])
        {
            color[v] = 3 - color[u];
            if(!bipartite(v)) return false;
        }
    }
    return true;
}
bool f(int lim)
{
    if(lim == m) return true;
    memset(color,0,sizeof(color));
    memset(head,-1,sizeof(head));
    maxedge = -1;
    for(int i=lim+1;i<=m;i++) addedge(road[i].u,road[i].v);
    for(int i=lim+1;i<=m;i++)
    {
        if(!color[road[i].u])
        {
            color[road[i].u]=1;
            if(!bipartite(road[i].u)) return false;
        }
        if(!color[road[i].v])
        {
            color[road[i].v]=1;
            if(!bipartite(road[i].v)) return false;
        }
    }
    return true;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) scanf("%d%d%d",&road[i].u,&road[i].v,&road[i].c);
    sort(road+1,road+m+1);
    int l=1,r=m;
    while(l<r)
    {
        int mid = (l+r)>>1;
        bool tmp = f(mid);
        if(tmp) r=mid;
        else l=mid+1;
    }
    if(l==1 && f(0))
    {
        printf("0");
        return 0;
    }
    printf("%d",road[l].c);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值