题目大意
给一张 \(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");
}
}