2020.07.18 牛客多校第三场

G.Operating on a Graph

题意:

给定一个n个点m条边的图,对每个点i一开始颜色都为i。现在给出q次操作,每次给出一种颜色col,对任意颜色x,若存在一个x颜色的点与col颜色的点直接相连,就要把所有x颜色的点都变成col颜色。求最终每个点的颜色。

思路:

首先,很容易想到要利用并查集,但是如何处理变色操作使复杂度不爆炸就需要仔细想想(比赛的时候帮队友去想数论了,挺可惜的)。

有一个重要的观察发现是,每个点对相邻点的更新操作最多只有一次,之后的操作中就不用再考虑这些点了。那么我们先把每种颜色的点存在一种数据结构中 ,如 \(a_1\)\(a_2\)\(a_3\) 。当对颜色1操作时,假设1颜色的点存在边连接到2、3颜色的点,我们就更新 \(a_1 = a_2+a_3\)\(a_2 = empty\)\(a_3=empty\) (2和3都变成了1),同时在并查集中更新 \(pre[2] = pre[3] = 1\)

由于q<=8e5,我们每次的合并只能O(1)。题解中给出的做法是用链表操作,代码如下。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 8e5 + 5;
const int MAXM = 8e5 + 5;
//前向星----------------------
struct edge {
    int to, next, w;
} e[MAXM * 2]; //双倍内存
int cnt = 0;
int head[MAXN];
void add_edge(int u, int v, int w) { //从1开始存
    e[++cnt].to = v;
    e[cnt].next = head[u];
    e[cnt].w = w;
    head[u] = cnt;
}
int pre[MAXN];
int find(int x) {
    while(x != pre[x])
        x = pre[x] = pre[pre[x]];
    return x;
}
struct node {
    node() {};
    node(int x) {
        val = x;
    };
    node* next = NULL;
    int val; // 点标号
};
node* First[MAXN];
node* Last[MAXN];
void init(int n) {
    for(int i = 0; i <= n; i++)
        pre[i] = i;
    cnt = 0;
    fill(head, head + n, 0);
    for(int i = 0; i <= n; i++) {
        First[i] = new node(i);
        Last[i] = First[i];
    }
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        init(n);
        for(int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v, 0);
            add_edge(v, u, 0);
        }

        int q;
        scanf("%d", &q);
        while(q--) {
            int x;
            scanf("%d", &x);
            node* fir = NULL;
            node* las = NULL;
            for(node* i = First[x]; i != NULL; i = i->next) {
                int u = i->val;
                for(int j = head[u]; j; j = e[j].next) {
                    int v = e[j].to;
                    int col = find(v);
                    if(col != x && First[col] != NULL) {
                        if(fir == NULL) {
                            fir = First[col];
                            las = Last[col];
                        } else {
                            las->next = First[col];
                            las = Last[col];
                        }
                        pre[col] = x;
                        First[col] = Last[col] = NULL;
                    }
                }
            }
            First[x] = fir;
            Last[x] = las;
        }
        for(int i = 0; i < n; i++)
            printf("%d ", find(i));
        printf("\n");
    }
}

但是鉴于链表本来就很少写,生疏的话会出现指针乱飘debug半天的情况(指我),因此并不推荐(看了看大哥们好像也没人用链表写)。我又想了一下,发现用树也可以做到O(1)合并,乱打一发就秒了,代码如下。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 8e5 + 5;
const int MAXM = 8e5 + 5;
//前向星----------------------
struct edge {
    int to, next, w;
} e[MAXM * 2]; //双倍内存
int cnt = 0;
int head[MAXN];
void add_edge(int u, int v, int w) { //从1开始存
    e[++cnt].to = v;
    e[cnt].next = head[u];
    e[cnt].w = w;
    head[u] = cnt;
}
//并查集-----------------------
int pre[MAXN];
int find(int x) {
    while(x != pre[x])
        x = pre[x] = pre[pre[x]];
    return x;
}
//------------------------------
vi ee[MAXN];
int root[MAXN];
void dfs(int u, int x) {
    for(int j = head[u]; j; j = e[j].next) {
        int w = e[j].to;
        int col = find(w);
        if(col != x) {
            if(root[x] == -1)
                root[x] = root[col];
            else
                ee[root[x]].pb(root[col]);
            root[col] = -1;
            pre[col] = x;
        }
    }
    for(int i = 0; i < SZ(ee[u]); i++) {
        int v = ee[u][i];
        dfs(v, x);
    }
}
void init(int n) {
    for(int i = 0; i <= n; i++)
        pre[i] = i;
    cnt = 0;
    fill(head, head + n, 0);
    for(int i = 0; i < n; i++) {
        ee[i].clear();
        root[i] = i;
    }
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        init(n);
        for(int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v, 0);
            add_edge(v, u, 0);
        }
        int q;
        scanf("%d", &q);
        while(q--) {
            int x;
            scanf("%d", &x);
            int rt = root[x];
            root[x] = -1;
            dfs(rt, x);
        }
        for(int i = 0; i < n; i++)
            printf("%d ", find(i));
        printf("\n");
    }
}
posted @ 2020-07-21 09:47  Hartley  阅读(40)  评论(0编辑  收藏  举报