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("
");
}
}
但是鉴于链表本来就很少写,生疏的话会出现指针乱飘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("
");
}
}