b_lg_关押囚犯 & 强盗团伙 & 修改数组 & 食物链(分类并查集:分离到各自的补集里面 | 记录敌人的兄弟 | 修改父亲的编号 | 增加偏置值)

一、关押囚犯

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≤1000000000 且每对罪犯组合只出现一次。

输出格式
输出共1行,为 Z 市长看到的那个冲突事件的影响力。
如果本年内监狱中未发生任何冲突事件,请输出0。
数据范围
N≤20000,M≤100000

输入样例:
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
输出样例:
3512

方法一:贪心+并查集

先对囚犯之间的怨气降序排序(这是题目要求的),然后两个囚犯如果不在同一个集合里面就将其各自安排在一个另一个集合中(有什么办法可以将两个元素完全隔离开来呢?肯定是安排到它们各自的补集里面吧),尽量将其隔离开(一山难容二虎),如果实在不行,则返回结果即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
struct node {
    ll u,v,w;
} e[N];
ll fa[N];
int find(int u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n,m; cin>>n>>m;
    for (int i=0; i<m; i++) cin>>e[i].u>>e[i].v>>e[i].w;
    sort(e, e+m, [&] (node A, node B) {
        return A.w>B.w;
    });
    for (int i=0; i<N; i++) fa[i]=i;
    for (int i=0; i<m; i++) {
        ll u=e[i].u, v=e[i].v, fu=find(u), fv=find(v);
        if (fu==fv) {
            return cout << e[i].w, 0;
        }   
        fa[fv]=find(u+n);
        fa[fu]=find(v+n);
    }
    cout << 0;
    return 0;
}

复杂度分析

  • Time\(O(mlogm)\)
  • Space\(O(n)\)

二、强盗团伙

如果两个强盗遇上了,那么他们要么是朋友,要么是敌人。
而且有一点是肯定的,就是:我朋友的朋友是我的朋友;我敌人的敌人也是我的朋友。
两个强盗是同一团伙的条件是当且仅当他们是朋友。
现在给你一些关于强盗们的信息,问你最多有多少个强盗团伙。

方法一:巧妙处理

这里核心在于处理敌人的敌人(某个人的敌人们互相之间都是朋友...),这里直接用一个数组 B 来存储我的敌人的兄弟,B[p]=0 时,表示a暂时没有敌人的大哥;B[p]=x 时,且输入字符为 E 时,就需要将 B[p] 和 b 合并,因为他们都是 p 的敌人;对于 q 也是如此

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int fa[N], rk[N], B[N];
int find(int u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
void merge(int u, int v) {
    int fu = find(u), fv = find(v);
    if (fu == fv)
        return;
    if (rk[fu] > rk[fv]) fa[fv] = fu, rk[fu]++;
    else                 fa[fu] = fv, rk[fv]++;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n,m; cin>>n>>m;
    for (int i=1; i<=n; i++) fa[i]=i;
    for (int i=0; i<m; i++) {
        char r; int p,q; cin>>r>>p>>q;
        if (r=='F') {
            merge(p,q);
        } else {
            if (B[p]==0) B[p]=find(q);
            else         merge(B[p], q);
            if (B[q]==0) B[q]=find(p);
            else         merge(p, B[q]);
        }
    }
    int ans=0;
    for (int i=1; i<=n; i++) if (fa[i]==i)
        ans++;
    cout << ans;
    return 0;
}

给定一个长度为 N 的数组 A = [A1, A2, · · · AN ],数组中有可能有重复出现 的整数。
现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改 A2,A3,··· ,AN。
当修改 Ai 时,小明会检查 Ai 是否在 A1 ∼ Ai−1 中出现过。如果出现过,则 小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直 到 Ai 没有在 A1 ∼ Ai−1 中出现过。
当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。 现在给定初始的 A 数组,请你计算出最终的 A 数组

输入
第一行包含一个整数 N。 第二行包含N个整数A1,A2,··· ,AN
输出
输出N个整数,依次是最终的A1,A2,··· ,AN

样例输入
5
2 1 1 3 4
样例输出
2 1 3 4 5

数据范围
对于 80% 的评测用例,1 ≤ N ≤ 10000
对于所有评测用例,1 ≤ N ≤ 100000,1 ≤ Ai ≤ 1000000

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int fa[N];
int find(int u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n; cin>>n;
    int A[n+1];
    for (int i=1; i<=n; i++) cin>>A[i];
    for (int i=1; i<N; i++) fa[i]=i;
    for (int i=1; i<=n; i++) {
        int f=find(A[i]);
        A[i]=f, fa[A[i]]=find(f+1);
    }    
    for (int i=1; i<=n; i++) cout << A[i] << ' ';
    return 0;
}

四、食物链

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。
A吃B, B吃C,C吃A。
你的任务是根据给定的N和K句话,输出假话的总数。
输入格式
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
输出格式
只有一个整数,表示假话的数目。
数据范围
1≤N≤50000,
0≤K≤100000

输入样例:
100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5
输出样例:
3

方法一:并查集

两种关系:同类,天地;
两个域:

  • x的捕食域,记为 x+n
  • x的天域,记为 x+n+n
import java.util.*;
import java.math.*;
import java.io.*;
public class Main{
    static class Solution {
        int N=(int) 1e6+5, fa[];
        int find(int u) {
            if (fa[u]==u) return u;
            return fa[u]=find(fa[u]);
        }
        void union(int x, int y) {
            int fx=find(x), fy=find(y);
            if (fx!=fy) {
                fa[fx]=fy;
            }
        }
        void init() {
            Scanner sc = new Scanner(new BufferedInputStream(System.in));
            BufferedWriter w = new BufferedWriter(new OutputStreamWriter(System.out));
            int n=sc.nextInt(), k=sc.nextInt(); fa=new int[N];
            for (int i=1; i<N; i++) fa[i]=i;

            int ans=0;
            for (int i=0; i<k; i++) {
                int d=sc.nextInt(), x=sc.nextInt(), y=sc.nextInt();
                if (x>n || y>n) {
                    ans++;
                } else if (d==1) { //x和y的捕食是同类 | x和y的天敌对象是一类的话,都是假的
                    if (find(x)==find(y+n) || find(x)==find(y+n+n)) {
                        ans++;
                    } else {
                        union(x,y);
                        union(x+n,y+n);
                        union(x+n+n,y+n+n);
                    }
                } else { //x=y | x和y是同类 | k=2为x吃y,如果y的捕食域中有x则为谎言
                    if (x==y || find(x)==find(y) || find(x)==find(y+n)) {
                        ans++;
                    } else {
                        union(x, y+n+n);  //x和y的天敌是一组
                        union(x+n, y);    //x的捕食对象和y是一组
                        union(x+n+n, y+n);//x的天敌对象和y的捕食对象是一组
                    }
                }
            }
            System.out.println(ans);
        }
    }
    public static void main(String[] args) throws IOException {  
        Solution s = new Solution();
        s.init();
    }
}
posted @ 2020-09-09 19:56  童年の波鞋  阅读(158)  评论(0)    收藏  举报