CF576E Painting Edges 题解

发布时间 2023-07-06 11:05:44作者: 霜木_Atomic

CF576E Painting Edges

关于我看完题解后改了一个位置就过题导致我都不知道我怎么过的
当然现在真的大彻大悟了。

题意

给定一张 \(n\) 个点,\(m\) 条边无向图,有 \(k\) 种颜色,初始每条边无颜色,给定 \(q\) 个操作,每个操作某条边染成颜色 \(c\)。只有一个操作合法,我们才能真正执行该操作,否则不执行。一个操作合法,当且仅当执行完所得的图,保留任意一种颜色的边(不包括无色边)所得的子图是二分图。求出每次操作是否会执行。

思路

首先观察到颜色数非常少,可以考虑对每个点都开 \(k\) 个并查集,利用并查集判断是否出现奇环;我们把所有操作都看作加边,然后就可以利用线段树分治模板(这道题)的思路解决了……
吗?
我们会发现这道题有一个不一样的地方:有些操作是无法执行的,这导致我们一开始挂上线段树的操作可能需要撤回。
显然,直接删除是不现实的。我们可以将删除改为维持原状,即把不合法的修改变成上一个合法修改。
然后,我们发现,对于每一个时间点我我们都只对应一个操作,这一个性质非常重要。我们对线段树 dfs 的时候,也是按照时间顺序去访问每个叶子节点的,也就是说,当我们访问到一个叶子节点的时候,会把之前的所有合法操作执行完,同时可以知道这个时刻对应的操作!
同样因为时序性,每个叶子节点的操作都应该最后被执行,那我们就可以考虑把第 \(i\) 次修改与第 \(j\) 次修改之间,\(i\) 次修改的作用区域设为 \([i+1, j-1]\)\(j-1\) 好理解,那 \(i+1\) 呢?这一步转化就是在保证之前所有的合法修改执行完才进行第 \(i\) 次修改,因为在这里需要先进行判断,如果合法才可以执行,而是否执行第 \(i\) 次修改并不影响判断。
这样,我们的问题就解决了。每次遍历到一个叶子节点,先进行判断,如果不合法,就把这个操作改成上一次操作后的颜色。
代码:

#include<bits/stdc++.h>
#define ls tr<<1
#define rs tr<<1 | 1 
using namespace std;
const int N = 5e5+100;

inline int read(){
	int x = 0; char ch = getchar();
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
	return x;
}
struct Edge{
	int u, v;
}e[N];
struct Node{
	int tk, c;
}op[N];

struct Revoke{
	int c, u, del;
};
stack<Revoke> s;
int top;
int fa[55][N<<1], dep[55][N<<1];
int n, m, K, Q;

vector<int> tree[N<<2];
void insert(int tr, int L, int R, int lq, int rq, int tk){
	if(L == lq && rq == R){
		tree[tr].push_back(tk);
		return;
	}
	int mid = (L+R)>>1;
	if(lq<=mid) insert(ls, L, mid, lq, min(rq, mid), tk);
	if(rq> mid) insert(rs, mid+1, R, max(lq, mid+1), rq, tk);
}


inline int find(int x, int col){
	while(x!=fa[col][x]) x = fa[col][x];
	return x;
}
void merge(int col, int x, int y){
	int fx = find(x, col), fy = find(y, col);
	if(fx == fy) return;
	if(dep[col][fx]>dep[col][fy]) swap(fx, fy);
	++top;
	Revoke tmp = (Revoke){col, fx, dep[col][fx] == dep[col][fy]};
	s.push(tmp);
	fa[col][fx] = fy;
	dep[col][fy]+=(dep[col][fx] == dep[col][fy]);
} 
bool ans[N];
int lstcol[N];
void solve(int tr, int L, int R){
	int last = s.size();
	for(int i = 0; i<tree[tr].size(); ++i){
		int opi = tree[tr][i];
		if(!op[opi].c) continue;//如果没有颜色就不进行修改
		int u = e[op[opi].tk].u, v = e[op[opi].tk].v;
		int col = op[opi].c;
		merge(col, u, v+n);
		merge(col, v, u+n);
	}
	if(L == R){//当我们遍历到叶子节点的时候,这个时刻对应的操作是未被执行的
	//而之前所有的合法操作都已经执行完毕,因此这时候是可以判断该次操作是否合法
		int col = op[L].c, tk = op[L].tk;
		int x = e[tk].u, y = e[tk].v;
		if(find(x, col) == find(y, col)){
			puts("NO");
			op[L].c = lstcol[tk];//如果不合法,直接把这次操作改为上次操作的颜色
		} else{
			puts("YES");
			lstcol[tk] = op[L].c;//记录最后一次合法修改的颜色
		}
	} else{
		int mid = (L+R)>>1;
		solve(ls, L, mid);
		solve(rs, mid+1, R);
	}
	while(s.size()>last){
		Revoke tmp = s.top();
		s.pop();
		int col = tmp.c, u = tmp.u, del = tmp.del;
		dep[col][fa[col][u]]-=del;
		fa[col][u] = u;
	}
}
int las[N];
int tot;
int main(){
	n = read(), m = read(), K = read(), Q = read();
	for(int i = 1; i<=m; ++i){
		e[i].u = read(), e[i].v = read();
	}
	for(int i = 1; i<=Q; ++i){
		op[i].tk = read(), op[i].c = read();
		if(las[op[i].tk] && las[op[i].tk]<i-1){
			insert(1, 1, Q, las[op[i].tk]+1, i-1, las[op[i].tk]);
		}
		las[op[i].tk] = i;
	}
	for(int i = 1; i<=m; ++i){
		if(las[i]<Q){
			insert(1, 1, Q, las[i]+1, Q, las[i]);
		}
	}
	for(int i = 1; i<=K; ++i){
		for(int j = 1; j<=n*2; ++j){
			fa[i][j] = j;
		}
	}
	solve(1, 1, Q);
	return 0;
}