P8531 [Ynoi2003] 戌亥彗星
特殊性质实际上就是保证了所有环外点度数都 \(\le2\),这样就只需要考虑前两个条件。注意到对于一个 \(i\),假设 \(i\) 为区间左端点,那么所有满足条件 \(2\) 的右端点构成一个区间,记为 \(l_i,r_i\),且满足 \(l_i\le l_{i+1},r_i\le r_{i+1}\)。
而且这些区间有更强的性质:如果 \(l_i<l_{i+1}\),则 \(r_i<l_{i+1}\)。证明也是不难的:如果 \(r_i\ge l_{i+1}\),那么 \([i,l_i]\) 和 \([i+1,l_{i+1}]\) 中的简单环在 \([i,r_i]\) 中也会出现。因为 \(l_i<l_{i+1}\),存在一个简单环包含了 \(i\),于是 \([i,r_i]\) 中有 \(>1\) 个简单环,与定义矛盾。
现在考虑加入条件 \(3\),对于每个 \(i\),同时满足条件 \(2,3\) 的右端点也形成一个区间,且区间左端点为 \(l_i\)。记这个区间为 \([l_i,r'_i]\),如果没有满足条件的右端点,记 \(r'_i=l_i-1\)。显然有 \(l_i-1\le r'_i\le r_i\)。
事实上 \(r'\) 也满足 \(r'_i\le r'_{i+1}\)。证明考虑如果 \(l_i=l_{i+1}\),根据定义就有单调性;如果 \(l_i<l_{i+1}\),那么 \(r'_i\le r_i\le l_{i+1}-1\le r'_{i+1}\)。
考虑怎么求 \(l\) 和 \(r'\)。由于两个序列都有单调性,可以类似双指针地做。具体来说,动态维护 \([l_i,r'_i]\) 的边组成的结构,每次从 \(i\) 推向 \(i-1\) 时,暴力移动右端点直到满足条件 \(2,3\)。如果求出的 \(r'_{i-1}<l_i\),那么再暴力移动右端点找出 \(l_{i-1}\),然后复原即可;否则有 \(l_{i-1}=l_i\)。因为如果 \(l_{i-1}<l_i\),那么 \(r'_{i-1}\le r_{i-1}<l_i\),所以暴力找 \(l\) 是对的。移动端点的总次数为 \(O(n)\)。
这个结构需要支持的操作为断边、加边和查询是否满足条件 \(2,3\),考虑 LCT。因为任意时刻都只有 \(\le1\) 个基环树,可以在森林的基础上用一个变量记录环边。条件 \(3\) 只需要支持查环上度数 \(\ge3\) 的点个数,再与整体比对,因为我们有了一条环边,可以直接查询两端点路径。条件 \(2\) 也是好判定的,加边时分讨一下两端是否连通,连通再讨论是否已经有环边即可。
现在我们已经求出了 \(l_i\) 和 \(r'_i\)。考虑把询问记录在左端点,然后扫描线。如果已经满足了条件 \(2,3\),条件 \(1\) 的限制就是恰有一个连通块。显然该连通块为一棵基环树,有 \(|V|=|E|\)。考虑在对 \(l\) 扫描时,对每个 \(r\),维护对应的 \(d_r=|V|-|E|\),同时记录合法的 \(l\) 个数 \(c_r\)。每次更新 \(d\) 后,找出所有在区间 \([l_i,r'_i]\) 且 \(d_p=0\) 的位置 \(p\),把对应的 \(c_p\leftarrow c_p+1\) 即可。因为我们已经保证了条件 \(2,3\),所以有 \(|V|-|E|\le0\)。每次就是相当于找出最大的 \(d_p\) 如果 \(=0\) 则将 \(c_p\leftarrow c_p+1\)。这个是容易用线段树维护的。
总时间复杂度为 \(O((n+q)\log n)\)。
#include <bits/stdc++.h>
#define ALL(x) begin(x), end(x)
#define All(x, l, r) &x[l], &x[r] + 1
using namespace std;
void file() {
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
}
using ll = long long;
template <typename T> using vec = vector<T> ;
const int kL = 1e6 + 5;
int n, q;
array<int, kL> l, r;
struct edge {
int u, v;
edge() { }
};
array<edge, kL> ed;
namespace Solve1 {
int cir;
array<int, kL> deg;
namespace LCT {
int cntall;
array<bool, kL> rev;
array<int, kL> fa, cnt;
array<array<int, 2>, kL> ch;
void pu(int x) { cnt[x] = cnt[ch[x][0]] + cnt[ch[x][1]] + (deg[x] >= 3); }
bool wc(int x) { return ch[fa[x]][1] == x; }
bool ir(int x) { return (ch[fa[x]][0] ^ x) && (ch[fa[x]][1] ^ x); }
void rot(int x) {
int y = fa[x], z = fa[y], l = wc(x), r = !l;
if(!ir(y)) ch[z][wc(y)] = x;
if(ch[y][l] = ch[x][r]) fa[ch[y][l]] = y;
fa[fa[ch[x][r] = y] = x] = z;
pu(y); pu(x);
}
void add(int x) { rev[x] ^= 1; swap(ch[x][0], ch[x][1]); }
void pd(int x) { if(rev[x]) { add(ch[x][0]); add(ch[x][1]); rev[x] = 0; } }
void PD(int x) { if(!ir(x)) PD(fa[x]); pd(x); }
void spl(int x) {
for(PD(x); !ir(x); rot(x))
if(!ir(fa[x])) rot((wc(x) ^ wc(fa[x])) ? x : fa[x]);
}
int acs(int x) {
int tx = x, t = 0;
for(; x; x = fa[t = x]) { spl(x); ch[x][1] = t; pu(x); }
return spl(tx), t;
}
void mkr(int x) { acs(x); add(x); }
void split(int x, int y) { mkr(y); acs(x); }
void link(int x, int y) { mkr(x); mkr(y); fa[x] = y; }
void cut(int x, int y) { split(x, y); ch[x][0] = fa[y] = 0; pu(x); }
int find(int x) {
for(acs(x); pd(x), ch[x][0]; x = ch[x][0]) ;
return spl(x), x;
}
int query(int x, int y) { return split(x, y), cnt[x]; }
void update(int x, int v) {
if(v > 0) {
if((deg[x] += v) == 3) { cntall++; acs(x); pu(x); }
}else {
if((deg[x] += v) == 2) { cntall--; acs(x); pu(x); }
}
}
bool insert(int id) {
int u = ed[id].u, v = ed[id].v;
if(find(u) == find(v)) {
if(!cir) return cir = id, 1;
else return 0;
}
return link(u, v), 1;
}
void erase(int id) {
if(cir == id) return void(cir = 0);
cut(ed[id].u, ed[id].v);
if(cir && insert(cir)) cir = 0;
}
bool check() {
if(!cir) return 0;
int u = ed[cir].u, v = ed[cir].v;
return query(u, v) == cntall;
}
}
using LCT::check;
bool insert(int id) {
if(LCT::insert(id)) {
LCT::update(ed[id].u, 1);
LCT::update(ed[id].v, 1);
return 1;
}
return 0;
}
void erase(int id) {
LCT::update(ed[id].u, -1);
LCT::update(ed[id].v, -1);
LCT::erase(id);
}
void solve() {
l[n] = n + 1; r[n] = n; insert(n);
for(int i = n - 1, p = n; i; i--) {
for(; p >= i; erase(p--))
if(insert(i)) {
r[i] = p;
if(!cir) l[i] = p + 1;
break;
}
if(l[i]) continue;
for(; p >= i; erase(p--))
if(check() || !cir) break;
r[i] = p;
if(!check()) { l[i] = p + 1; continue; }
if((r[i] < l[i + 1]) || (l[i + 1] > r[i + 1])) {
int tp = p;
for(; p >= i; erase(p--))
if(!check()) { l[i] = p + 1; break; }
for(; p < tp; insert(++p)) ;
}else l[i] = l[i + 1];
}
}
}
namespace Solve2 {
const int sL = 4e6 + 5;
array<ll, kL> ans;
array<int, kL> last;
array<vec<pair<int, int>>, kL> qry;
#define ls (o << 1)
#define rs (o << 1 | 1)
struct SGT {
array<int, sL> mx, t1, c;
array<ll, sL> s, t2;
void pu(int o) {
s[o] = s[ls] + s[rs];
mx[o] = max(mx[ls], mx[rs]);
c[o] = (mx[ls] >= mx[rs]) * c[ls] + (mx[rs] >= mx[ls]) * c[rs];
}
void build(int o, int l, int r) {
c[o] = r - l + 1;
if(l == r) return ;
int mi = (l + r) >> 1;
build(ls, l, mi); build(rs, mi + 1, r);
}
void ad1(int o, int v) { mx[o] += v; t1[o] += v; }
void ad2(int o, ll v) { s[o] += v * c[o]; t2[o] += v; }
void pd(int o) {
if(int& x = t1[o]) { ad1(ls, x); ad1(rs, x); x = 0; }
if(ll& x = t2[o]) {
if(mx[ls] >= mx[rs]) ad2(ls, x);
if(mx[ls] <= mx[rs]) ad2(rs, x);
x = 0;
}
}
void update(int o, int l, int r, int x, int y, int v) {
if((l > y) || (r < x)) return ;
if((l >= x) && (r <= y)) return ad1(o, v);
pd(o); int mi = (l + r) >> 1;
update(ls, l, mi, x, y, v); update(rs, mi + 1, r, x, y, v);
pu(o);
}
void update2(int o, int l, int r, int x, int y, int v) {
if((l > y) || (r < x) || (mx[o] < 0)) return ;
if((l >= x) && (r <= y)) return assert(mx[o] == 0), ad2(o, v);
pd(o); int mi = (l + r) >> 1;
update2(ls, l, mi, x, y, v); update2(rs, mi + 1, r, x, y, v);
pu(o);
}
ll query(int o, int l, int r, int x, int y) {
if((l > y) || (r < x)) return 0;
if((l >= x) && (r <= y)) return s[o];
pd(o); int mi = (l + r) >> 1;
return query(ls, l, mi, x, y) + query(rs, mi + 1, r, x, y);
}
}sgt;
void solve() {
for(int i = 1, l, r; i <= q; i++) {
cin >> l >> r;
qry[l].emplace_back(r, i);
}
last.fill(n + 1);
sgt.build(1, 1, n);
for(int i = n; i; i--) {
sgt.update(1, 1, n, i, n, 1);
sgt.update(1, 1, n, i, last[ed[i].u] - 1, -1);
sgt.update(1, 1, n, i, last[ed[i].v] - 1, -1);
sgt.update2(1, 1, n, l[i], r[i], 1);
for(auto k : qry[i]) {
int p = k.first, id = k.second;
ans[id] = sgt.query(1, 1, n, i, p);
}
last[ed[i].u] = last[ed[i].v] = i;
}
for(int i = 1; i <= q; i++) cout << ans[i] << "\n";
}
}
int main() {
// file();
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> ed[i].u >> ed[i].v;
cin >> q;
Solve1::solve();
Solve2::solve();
return 0;
}