CF1386C Joker

CF1386C Joker

题目大意

给一张 \(n\) 个点 \(m\) 条边的无向图。\(q\) 次询问,删去编号在 \([l,r]\) 内的边,问剩下的图是否存在奇环。

Solution

没能自己搞出来,参考了 这篇题解,是我菜了。

之前偷得懒现在都得还啊……如果会 P5787 二分图 /【模板】线段树分治 的 LCT 写法,这题就轻松一些。

首先注意到题目中是有单调性的。对于一个固定的 \(r\),存在一个临界点 \(it_r\),\(l\ge it_r\) 时存在奇环,否则不存在。

显然 \(it_r\) 是随着 \(r\) 的增大单调不降的,所以就直接拿双指针扫。

那么现在要维护的是,删边,加边,判断这张图是否有奇环,并且删边加边的顺序是一个队列状物。

这个队列状使得我们可以维护删边时间的最大生成树。思考一下哪些边更优。

  • 左端点右移加入的边之后都不会删除,也就是删除时间为 \(+\infty\),能加就加。

  • 右端点右边的边是可以被删除的,而且编号越大删除时间越晚。

所以边权就这么赋:左端点处加入的边设 \(+\infty\),右端点右侧的边设为编号。

然后 LCT 维护最大生成树即为最优的形态。

但是怎么判断奇环呢?

考虑加边的时候,如果不成环直接加。

成环,判断一下两点间距离就知道是否是奇环。

但是如果有好多条边加入都能产生奇环岂不是很难维护?

这个就是 P5787 的维护方法了:在这条边加入时间,和这个环上最早被删除的边的删除时间这段区间内,都能产生一个奇环,差分一下就好了。

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
#define sz(v) (int)(v).size()
typedef long long LL;
typedef double db;
template<class T>bool ckmax(T&x,T y){return x<y?x=y,1:0;}
template<class T>bool ckmin(T&x,T y){return x>y?x=y,1:0;}
#define rep(i,x,y) for(int i=x,i##end=y;i<=i##end;++i)
#define per(i,x,y) for(int i=x,i##end=y;i>=i##end;--i)
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	return f?x:-x;
}
const int N = 400005;
int n, m, q, it[N], val[N], siz[N], id[N], fa[N], ch[N][2], num, dif[N];
bool fl[N], lk[N];
struct {int x, y;} a[N];
inline bool nrt(int x) { return ch[fa[x]][0] == x || ch[fa[x]][1] == x; }
inline void pushup(int x) {
	id[x] = x;
	if(val[id[ch[x][0]]] < val[id[x]]) id[x] = id[ch[x][0]];
	if(val[id[ch[x][1]]] < val[id[x]]) id[x] = id[ch[x][1]];
	siz[x] = (x > n) + siz[ch[x][0]] + siz[ch[x][1]]; 
}
inline void rotate(int x) {
	int y = fa[x], z = fa[y], k = ch[y][1] == x, w = ch[x][!k];
	if(nrt(y)) ch[z][ch[z][1] == y] = x;
	ch[y][k] = w, ch[x][!k] = y;
	fa[w] = y, fa[y] = x, fa[x] = z;
	pushup(y), pushup(x);
}
inline void pushdown(int x) {
	if(fl[x]) {
		swap(ch[x][0], ch[x][1]), fl[x] = 0;
		fl[ch[x][0]] ^= 1, fl[ch[x][1]] ^= 1;
	}
}
inline void splay(int x) {
	static int top, stk[N], y, z;
	stk[top = 1] = y = x;
	while(nrt(y)) stk[++top] = y = fa[y];
	while(top) pushdown(stk[top--]);
	while(nrt(x)) {
		y = fa[x], z = fa[y];
		if(nrt(y)) rotate((ch[z][1] == y) ^ (ch[y][1] == x) ? x : y);
		rotate(x);
	}
}
inline void access(int x) {
	for(int y = 0; x; x = fa[y = x])
		splay(x), ch[x][1] = y, pushup(x);
}
inline void makeroot(int x) {
	access(x), splay(x), fl[x] ^= 1;
}
inline void link(int x, int y) {
	makeroot(x), fa[x] = y;
}
inline void split(int x, int y) {
	makeroot(x), access(y), splay(y);
}
inline void cut(int x, int y) {
	split(x, y), fa[x] = ch[y][0] = 0;
}
inline int findroot(int x) {
	access(x), splay(x);
	while(ch[x][0]) x = ch[x][0], pushdown(x);
	return splay(x), x;
}
inline bool connected(int x, int y) {
	makeroot(x);
	return findroot(y) == x;
}

signed main() {
	n = read(), m = read(), q = read();
	rep(i, 0, n) val[i] = N + 1;
	rep(i, 1, m) a[i].x = read(), a[i].y = read();
	int st = m + 1;
	per(i, m, 1) {
		int x = a[i].x, y = a[i].y;
		if(!connected(x, y)) {
			val[i + n] = i, link(x, i + n), link(y, i + n), lk[i] = 1;
		} else {
			split(x, y);
			if(siz[y] % 2 == 0) {
				st = i;
				break;
			}
		}
	}
	if(st > m) {
		rep(i, 1, m) it[i] = m + 1;
	}
	for(int i = st, j = 1; i <= m; ++i) {
		num += dif[i];
		while(!num && j <= i) {
			int x = a[j].x, y = a[j].y;
			if(connected(x, y)) {
				split(x, y);
				int tmp = id[y];
				if(siz[y] % 2 == 0) {
					++num;
					if(val[tmp] != N) --dif[tmp - n];
				}
				lk[tmp - n] = 0;
				cut(tmp, a[tmp - n].x);
				cut(tmp, a[tmp - n].y);
			}
			val[j + n] = N;
			link(x, j + n), link(y, j + n), lk[j] = 1;
			++j;
		}
		it[i] = j;
		if(i < m && lk[i + 1]) {
			int x = a[i + 1].x, y = a[i + 1].y;
			cut(x, i + 1 + n), cut(y, i + 1 + n);
			lk[i + 1] = 0;
		}
	}
	while(q--) {
		int x = read(), y = read();
		puts(x >= it[y] ? "YES" : "NO");
	}
}
上一篇:算法 字符串【牛客网HJ88 扑克牌比大小 巧用string容器】


下一篇:Joker恶意软件的最新攻击技巧:使用Github隐藏有效载荷