jzoj6327 数颜色 (LCT)

题面

在这里插入图片描述
n , m , q ≤ 1 0 5 n,m,q \le 10^5 n,m,q105

分析

很容易想出 n log ⁡ 2 n n\log^2 n nlog2n的做法,用点减边的方法求连通块数,比如:

  1. 树剖后维护颜色块,从右往左枚举左端点后维护右端点答案。
  2. 用LCT的access操作代替上述做法的树剖。注意此处不能make_root,因此事实上操作数量还要翻倍…

由于树剖的log很难卡满,因此上述两做法还是可以通过此题的(甚至比一个log还快,小粉兔真牛批,其实是我打搓了

林立给出了一个log的做法:
注意到两条路径相交,深度大的LCA必然被覆盖。
同理,整个连通块只有最高的LCA不会被其他路径覆盖(LCA重合除外)。
因此,只要计算出某个机器人的LCA在哪个区间内没有覆盖,就可以解决此题了。

求左边第一个覆盖此点的机器人,只需要LCT的链赋值即可。
因此左右扫两遍,再加一个统计答案的操作就可以 O ( n log ⁡ n ) O(n \log n) O(nlogn)了。

注意LCA重合的情况,这时候我们只需要默认编号小的覆盖编号大的即可。

总结

将连通块个数转化为没有被覆盖的LCA个数,是本题的关键。

#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10;
int n,m,q;
void read(int &x) {
	char c; while((c=getchar()) > '9' || c < '0');
	x = c - '0'; while((c=getchar()) >= '0' && c <= '9') x = x * 10 + c - '0';
}
namespace tree{
	int final[N], nex[N * 2], to[N * 2], tot;
	void link(int x,int y) {
		to[++tot] = y, nex[tot] = final[x], final[x] = tot;
	}
	int g[N][17], dep[N], dfn[N], stm, R[N];
	void dfs(int x) {
		dfn[x] = ++ stm;
		dep[x] = dep[g[x][0]] + 1;
		for(int i = 1; i < 17; i++) g[x][i] = g[g[x][i - 1]][i - 1];
		for(int i = final[x]; i; i = nex[i]) {
			int y = to[i]; if (y != g[x][0]) {
				g[y][0] = x;
				dfs(y);
			}
		}
		R[x] = stm;
	}

	int lca(int x,int y,int &u,int &v){
		int fl = 0;
		if (dep[x] < dep[y]) fl = 1, swap(x, y);
		for(int i = 16; ~i; i--) if (dep[g[x][i]] >= dep[y])
			x = g[x][i];
		for(int i = 16; ~i; i--) if (g[x][i] != g[y][i]) {
			x = g[x][i], y = g[y][i];
		}
		u = x, v = y;
		if (fl) swap(u, v);
		return g[x][0];
	}

	int jump(int fa, int x) {
		for(int i = 16; ~i; i--) if (dep[g[x][i]] > dep[fa]) x = g[x][i];
		return x;
	}
}

pair<int,int> s[N], es[N][2];
int ac[N];
vector<pair<int,int> > qry[N];

int ans[N];
namespace BIT{
	#define lowbit(x) ((x) & -(x))
	int bit[N];
	inline int query(int x) {
		x++;
		int ret = 0; for(; x; x -= lowbit(x)) {
			ret += bit[x];
		}
		return ret;
	}
	inline void change(int x,int v) {
		x++;
		for(; x <= n + 1; x += lowbit(x)) bit[x] += v;
	}
}

namespace LCT{
	int se[N], fa[N], c[N][2], tag[N], v[N];

	#define c0 c[x][0]
	#define c1 c[x][1]

	void rev(int x) {
		if(!x)return;
		tag[x] ^= 1;
		swap(c0, c1);
	}

	void setvalue(int x, int vv) {
		if(!x)return;
		v[x] = se[x] = vv;
	}

	void down(int x) {
		if (tag[x]) {
			rev(c0), rev(c1);
			tag[x] = 0;
		}
		if (se[x]) {
			setvalue(c0, se[x]);
			setvalue(c1, se[x]);
			se[x] = 0;
		}
	}

	#define lr(x) ((c[fa[x]][1]) == (x))
	#define isroot(x) (c[fa[x]][0] != (x) && c[fa[x]][1] != (x))

	void rotate(int x) {
		int y = fa[x], z = lr(x);
		c[y][z] = c[x][1 - z];
		if (c[x][1 - z]) fa[c[x][1 - z]] = y;

		if (!isroot(y)) c[fa[y]][lr(y)] = x;
		fa[x] = fa[y];

		c[x][1 - z] = y;
		fa[y] = x;
	}

	void splay(int x) {
		static int Q[N]; int z=0;
		for(z=x;!isroot(z);z=fa[z]) {
			Q[++Q[0]] = z;
		}down(z);
		while(Q[0])down(Q[Q[0]--]);
		while(!isroot(x)) {
			if(!isroot(fa[x])){
				if(lr(x)==lr(fa[x])) rotate(fa[x]); else rotate(x);
			}
			rotate(x);
		}
	}

	void access(int x) {
		for(int i = 0; x; i = x, x = fa[x]) {
			splay(x);
			c[x][1] = i;
		}
	}

	void make_root(int x) {
		access(x), splay(x);
		rev(x);
	}
}

int L[N], sR[N];
void solve() {
	using namespace LCT;
	for(int i = 1; i <= n; i++) fa[i] = tree::g[i][0];
	for(int i = 1; i <= m; i++) {
		splay(ac[i]);
		L[i] = v[ac[i]];
		int a = s[i].first, b = s[i].second;
		make_root(a), access(b), splay(b);
		setvalue(b, i);
	}
	for(int i = 1; i <= n; i++) se[i] = 0, v[i] = m + 1;
	for(int i = m; i; i--) {
		splay(ac[i]);
		sR[i] = v[ac[i]];
		for(int j = 0; j < 2; j++) {
			int a = es[i][j].first, b = es[i][j].second;
			if (!a) continue;
			make_root(a), access(b), splay(b);
			setvalue(b, i);
		}
	}
}

vector<int> del[N];
int main() {
	freopen("tree.in","r",stdin);
	// freopen("tree.out","w",stdout);
	cin>>n>>m>>q;
	for(int i = 1; i < n; i++) {
		int u,v; read(u), read(v);
		tree::link(u,v);
		tree::link(v,u);
	}
	tree::dfs(1);
	for(int i = 1; i <= m; i++) {
		int a = 0, b = 0;
		read(a),read(b);
		using namespace tree;
		if (dfn[a] > dfn[b]) swap(a, b);
		s[i] = make_pair(a, b);
		if (dfn[b] <= R[a]) {
			ac[i] = a;
			if (a == b) continue;
			int g = tree::jump(a, b);
			es[i][0] = make_pair(g, b);
		} else {
			int u = 0, v = 0;
			int g = ac[i] = tree::lca(a,b,u,v);
			es[i][0] = make_pair(u, a);
			es[i][1] = make_pair(v, b);
		}
	}
	for(int i = 1; i <= q; i++) {
		int l,r; read(l), read(r);
		qry[l].push_back(make_pair(r, i));
	}
	solve();
	using namespace BIT;
	for(int i = n; i; i--) {
		change(i, 1);
		change(sR[i], -1);
		del[L[i]].push_back(i);
		for(int z : del[i]) {
			change(z, -1);
			change(sR[z], 1);
		}
		for(pair<int,int> z : qry[i]) {
			ans[z.second] = query(z.first);
		}
	}
	for(int i = 1; i <= q; i++) printf("%d\n", ans[i]);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值