首页 > 试题广场 >

[NOIP2010]关押罪犯

[编程题][NOIP2010]关押罪犯
  • 热度指数:1185 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 128M,其他语言256M
  • 算法知识视频讲解

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

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

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


输入描述:
第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。 
接下来的M行每行为三个正整数aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj
数据保证1≤aj<bj≤N,0<cj≤1,000,000,000,且每对罪犯组合只出现一次。


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

输入

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出

3512

说明


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

备注:
对于30%的数据有N≤15。
对于70%的数据有N≤2000,M≤50000。
对于100%的数据有N≤20000,M≤100000。
这道题是经典的带权并查集,可以首先贪心,从怨气大的对开始放置囚犯,将已放置的囚犯放进并查集中,更新他们与父节点之间的关系,即用权值表示与父节点是否在同一个监狱内。如果当前的两个囚犯在同一个并查集中,那么检查他们与根节点(此时他们的父节点也就是根节点)的关系,如果都为与根节点在同一个监狱或者在不同的监狱,那么这两个囚犯被关押在同一个监狱,将发生冲突,此时输出当前囚犯对的积怨,就是最优的代价。

#include <iostream> #include <algorithm> #include <vector> #define MAX 20005 using namespace std;
struct node{     int a, b;     int cost; };
int cmp(node a, node b){     return a.cost > b.cost; }
int parent[MAX] = {}; int relation[MAX] = {}; // 用relation[x]记录x和parent[x]关押监狱的情况 // 0表示关在不同的监狱,1表示关在相同的监狱,发生冲突
void init(int n){     for (int i=0; i<=n; i++){         parent[i] = -1;         relation[i] = 1;         // 初始时,都默认自己(和自己)在相同监狱         // 如果默认为0,会出现问题,可模拟运算     } }
int find(int x){     if (parent[x] == -1)         return x;     else{         int temp = find(parent[x]);         relation[x] = (relation[x] + relation[parent[x]] + 1) % 2;         parent[x] = temp;         return temp;     } }
int main(){     int n,m;     node temp;     vector<node> ip;     cin >> n >> m;     init(n);
    for (int i=0; i<m; i++){         int a, b, cost;         cin >> a >> b >> cost;         temp.a = a;         temp.b = b;         temp.cost = cost;         ip.push_back(temp);     }
    sort(ip.begin(), ip.end(), cmp);
    int ans = 0;     for (int i=0; i<ip.size(); i++){         int a = ip[i].a;         int b = ip[i].b;         int cost = ip[i].cost;         int pa = find(a);         int pb = find(b);
        if (pa == pb){             // 如果a和b同类,即与根节点的关系相同,会发生冲突             //(要么都在同一个监狱,要么都不在同一个监狱)             if (relation[a] == relation[b]){                 ans = cost;                 break;             }         }         else{             parent[pa] = pb;             relation[pa] = (0 + relation[b] - relation[a]) % 2;         }         // cout << (pa == pb) << ' ' << relation[a] << ' ' << relation[b] << ' ' << cost << endl;     }
    cout << ans << endl; }

发表于 2019-05-15 22:06:34 回复(0)
这题是并查集的一个变题,先按积怨值从大到小排序,然后一个一个看能否完全分开,遇到的第一个不能分开的囚犯对(如果强行分开就必然有更高的积怨值出现)就是答案。
一开始想到的是按监狱数量弄个并查集,后来发现并不行,因为如果要分开一对囚犯,没办法决定谁一定住1号监狱,谁一定住2号监狱。后来试了下用囚犯数量弄并查集,发现也不行,因为没有积怨的才能放一个集合里,比如1和2有积怨不在一起,3和4有积怨不在一起,那1和3,1和4等等就没法确定,那把他们都放不同集合里?不行,因为不在一个集合的可能有积怨可能没积怨。
让后我就想当选取到i + 1对囚犯时,前i对囚犯必然也形成一张图,这张图可能不是连通的,换句话说,就是包含多个极大联通子图(囚犯小团体),小团体与小团体之间互相没有积怨,因为程序已经选取到了i + 1对囚犯,所以这些小团体内部必然可以两两分开以致于没有积怨。选取其中一个小团体,如果这个极大联通子图没有坏,那必然可以变形成如下形式:
也就是说,肯定可以一刀切。
如果有环,那促成环的这条线的两端必分别属于左右两边:

如果这个时候来了一条边,它的两个端点都在这张图的一边:

那这张图必然怎么切都切不开了。
也就是说,如果第i + 1对囚犯都属于某一个小团体的一边,答案就出来了。
也就是说每名囚犯应该有2个状态i和i',上面的图应该是这样:

于是并查集的长度应该为2n,前n个表示1~n,后n个表示1'~n'。
PS:代码中并没有判断是否在都一边而是直接判断在不在一个集合,这是因为是直接查找的同一边的点,就是没有'的那边,这样直接判断在不在一个集合就可以了。
#include <bits/stdc++.h>
using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define rFor(i,t,s) for (int i = (t); i >= (s); --i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)
#define rforeach(i,c) for (__typeof(c.rbegin()) i = c.rbegin(); i != c.rend(); ++i)

#define pr(x) cout << #x << " = " << x << "  "
#define prln(x) cout << #x << " = " << x << endl

#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())

#define ms0(a) memset(a,0,sizeof(a))
#define msI(a) memset(a,inf,sizeof(a))

#define pii pair<int,int> 
#define piii pair<pair<int,int>,int> 
#define mp make_pair
#define pb push_back
#define fi first
#define se second

inline int gc(){
    static const int BUF = 1e7;
    static char buf[BUF], *bg = buf + BUF, *ed = bg;
    
    if(bg == ed) fread(bg = buf, 1, BUF, stdin);
    return *bg++;
} 

inline int ri(){
    int x = 0, f = 1, c = gc();
    for(; c<48||c>57; f = c=='-'?-1:f, c=gc());
    for(; c>47&&c<58; x = x*10 + c - 48, c=gc());
    return x*f;
}

typedef long long LL;
const int maxN = 1e5 + 7;

struct Edge{
    int X, Y, W;
    
    Edge() {}
    Edge(int x, int y, int w) : X(x), Y(y), W(w) {}
    
    bool operator < (const Edge &x) const {
        return W > x.W;
    }
};

int n, m, ans;
Edge e[maxN];
int f[maxN];

int Find(int x){
    while (x != f[x]) x = f[x] = f[f[x]];
    return x;
} 

int main(){
    n = ri();
    m = ri();
    
    For(i, 1, m) {
        e[i].X = ri();
        e[i].Y = ri();
        e[i].W = ri();
    }
    sort(e+1, e+m+1);
    For(i, 1, n<<1) f[i] = i;
    
    For(i, 1, m) {
        int x = e[i].X, y = e[i].Y;
        x = Find(x);
        y = Find(y);
        if(x == y) {
            ans = e[i].W;
            break;
        }
        f[x] = Find(e[i].Y + n);
        f[y] = Find(e[i].X + n);
    }
    
    printf("%d\n", ans);
    return 0;
}
发表于 2019-04-09 17:00:09 回复(0)