「BZOJ3514」Codechef MARCH14 GERALD07加强版-lct+二维数点

Description

n n n个点 m m m条边的无向图,询问保留图中编号在 [ l , r ] [l,r] [l,r]的边的时候图中的联通块个数。

n , m ≤ 200000 n,m \leq 200000 n,m200000

Solution

考虑常见套路:联通块数等于点数 − - 树边数。

从小到大加边,维护最大生成树。求出 p r e i pre_i prei表示 i i i这条边需要删除哪条边, p r e i = 0 pre_i=0 prei=0表示没有删除任何边。

对于一组询问 [ l , r ] [l,r] [l,r],如果 p r e i < l ≤ i ≤ r pre_i<l\leq i \leq r prei<lir,那么 i i i这条边一定在把 [ l , r ] [l,r] [l,r]里所有的边加上后的最小生成树上。二维数点即可。

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 400005;

int n, m, q, tp, eu[maxn], ev[maxn], val[maxn];

int f[maxn], ch[maxn][2], rev[maxn], Min[maxn];

#define get(x) (ch[f[x]][1] == x)
#define is_root(x) (ch[f[x]][0] != x && ch[f[x]][1] != x)
#define update(x) (Min[x] = min(val[x], min(Min[ch[x][0]], Min[ch[x][1]])))

inline void rotate(int x)
{
	int fa = f[x], gfa = f[fa], k = get(x);
	ch[fa][k] = ch[x][k ^ 1]; f[ch[x][k ^ 1]] = fa;
	ch[x][k ^ 1] = fa; 
	if (!is_root(fa)) ch[gfa][get(fa)] = x;
	f[fa] = x; f[x] = gfa;
	update(fa);
}

#define push_rev(x) (swap(ch[x][0], ch[x][1]), rev[x] ^= 1)

inline void pushdown(int x)
{
	if (!rev[x]) return ;
	if (ch[x][0]) push_rev(ch[x][0]);
	if (ch[x][1]) push_rev(ch[x][1]);
	rev[x] = 0;
}

inline void splay(int x)
{
	static int stk[maxn], top, fa;
	stk[top = 1] = x;
	while (!is_root(x)) stk[++top] = x = f[x];
	while (top) pushdown(stk[top--]);
	x = stk[1];
	while (!is_root(x)) {
		fa = f[x];
		if (!is_root(fa))
			get(x) ^ get(fa) ? rotate(x) : rotate(fa);
		rotate(x);
	}
	update(x);
}

inline void access(int x)
{
	for (int y = 0; x; y = x, x = f[x])
		splay(x), ch[x][1] = y, update(x);
}

inline void make_root(int x) {access(x); splay(x); push_rev(x);}
inline void link(int x, int y) {make_root(x); make_root(y); f[y] = x;}
inline void cut(int x, int y) {make_root(x); access(y); splay(y); ch[y][0] = f[x] = 0;}
inline int query(int x, int y) {make_root(x); access(y); splay(y); return Min[y];}
inline int find(int x) {access(x); splay(x); while (ch[x][0]) x = ch[x][0]; splay(x); return x;}

int pre[maxn];

void kruscal()
{
	Min[0] = 1e9;
	for (int i = 1; i <= n; ++i) Min[i] = val[i] = 1e9;
	for (int i = 1; i <= m; ++i) Min[i + n] = val[i + n] = i;
	for (int i = 1; i <= m; ++i) {
		if (eu[i] == ev[i]) continue;
		if (find(eu[i]) == find(ev[i])) {
			pre[i] = query(eu[i], ev[i]);
			cut(pre[i] + n, eu[pre[i]]);
			cut(pre[i] + n, ev[pre[i]]);
		}
		link(i + n, eu[i]);
		link(i + n, ev[i]);
	}
}

int rt[maxn], lch[maxn * 60], rch[maxn * 60], sum[maxn * 60], tot;

#define mid ((l + r) >> 1)

void insert(int &s, int l, int r, int p)
{
	++tot;
	lch[tot] = lch[s]; rch[tot] = rch[s]; sum[tot] = sum[s] + 1;
	s = tot;
	if (l == r) return ;
	if (p <= mid) insert(lch[s], l, mid, p);
	else insert(rch[s], mid + 1, r, p);
}

int query(int s1, int s2, int l, int r, int p)
{
	if (r <= p) return sum[s2] - sum[s1];
	if (p <= mid) return query(lch[s1], lch[s2], l, mid, p);
	else return query(lch[s1], lch[s2], l, mid, p) + query(rch[s1], rch[s2], mid + 1, r, p);
}

int main()
{
	//freopen("gerald.in", "r", stdin);
	freopen("8.in", "r", stdin);
	freopen("gerald.out", "w", stdout);
	
	n = gi(); m = gi(); q = gi(); tp = gi();
	for (int i = 1; i <= m; ++i) eu[i] = gi(), ev[i] = gi();
	
	kruscal();
	
	for (int i = 1; i <= m; ++i) {
		rt[i] = rt[i - 1];
		if (eu[i] != ev[i]) insert(rt[i] = rt[i - 1], 0, m, pre[i]);
	}

	int lstans = 0;
	for (int l, r, i = 1; i <= q; ++i) {
		l = gi() ^ lstans; r = gi() ^ lstans;
		printf("%d\n", lstans = (n - query(rt[l - 1], rt[r], 0, m, l - 1)));
		if (!tp) lstans = 0;
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值