【BZOJ4855】【JSOI2016】轻重路径

【题目链接】

【思路要点】

  • 考虑将问题离线,将删点变成加点。
  • 由题,一条从任意一个点到根的路径上至多有\(O(LogN)\)条轻边,而加入一个点不会使其到根路径上的重边变成轻边,因此加入一个点至多改变\(O(LogN)\)个点的轻重划分。
  • 对原树进行树链剖分,用树状数组支持询问某一个点当前的子树大小以及一条原树重链上轻边的位置。
  • 每加入一个点后找到该点到根路径上所有的轻边,并更新它们即可。
  • 还有一个小问题,就是题目要求“删点后出现两个子树大小一样的点时保留原有轻重路径划分”。
  • 当\(x\)的左右儿子\(L\)和\(R\)大小相同时,我们可以用DFS序+线段树找到\(L\)和\(R\)子树中下一个被加入节点的时刻,若\(L\)较靠前,则\(L\)是重儿子,若\(R\)较靠前,则\(R\)是重儿子,若两个子树中均不会再加入节点,则\(L\)是重儿子。
  • 时间复杂度\(O(NLog^2N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct BinaryIndextree {
	int a[MAXN], n;
	int Log, bit[MAXN];
	void init(int x) {
		n = x; bit[Log = 0] = 1;
		while (bit[Log] * 2 <= n) {
			Log++;
			bit[Log] = bit[Log - 1] << 1;
		}
		memset(a, 0, sizeof(a));
	}
	void modify(int pos, int d) {
		for (int i = pos; i <= n; i += i & -i)
			a[i] += d;
	}
	int query(int l, int r) {
		int ans = 0;
		for (int i = r; i >= 1; i -= i & -i)
			ans += a[i];
		for (int i = l - 1; i >= 1; i -= i & -i)
			ans -= a[i];
		return ans;
	}
	int pred(int pos, int val) {
		if (val == 0) return 0;
		int ans = 0;
		for (int i = Log; i >= 0; i--)
			if (ans + bit[i] <= n && a[ans + bit[i]] < val) {
				ans += bit[i];
				val -= a[ans];
			}
		return ans + 1;
	}
} Size, Light;
struct SegmentTree {
	struct Node {
		int lc, rc;
		int Max;
	} a[MAXN * 2];
	int root, size, n;
	void build(int &root, int l, int r) {
		root = ++size;
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
	}
	void update(int root) {
		a[root].Max = max(a[a[root].lc].Max, a[a[root].rc].Max);
	}
	void init(int x) {
		n = x;
		size = root = 0;
		build(root, 1, n);
	}
	void modify(int root, int l, int r, int pos, int val) {
		if (l == r) {
			a[root].Max = val;
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) modify(a[root].lc, l, mid, pos, val);
		else modify(a[root].rc, mid + 1, r, pos, val);
		update(root);
	}
	void modify(int pos, int val) {
		modify(root, 1, n, pos, val);
	}
	int query(int root, int l, int r, int ql, int qr) {
		if (l == ql && r == qr) return a[root].Max;
		int mid = (l + r) / 2, ans = 0;
		if (mid >= ql) chkmax(ans, query(a[root].lc, l, mid, ql, min(mid, qr)));
		if (mid + 1 <= qr) chkmax(ans, query(a[root].rc, mid + 1, r, max(mid + 1, ql), qr));
		return ans;
	}
	int query(int l, int r) {
		return query(root, 1, n, l, r);
	}
} ST;
struct Node {
	int lc, rc;
	int father;
	int dfn, rit;
	int up, size;
} a[MAXN];
int n, q, timer, pos[MAXN], home[MAXN];
long long ans[MAXN], curr;
bool vis[MAXN], intree[MAXN];
void renew(int pos) {
	int size = Size.query(a[pos].dfn, a[pos].rit);
	int f = a[pos].father;
	if (a[f].lc == pos) {
		int tmp = a[f].rc, tsize = Size.query(a[tmp].dfn, a[tmp].rit);
		if (!intree[tmp] || size > tsize) {
			Light.modify(a[pos].dfn, -1);
			if (intree[tmp]) Light.modify(a[tmp].dfn, 1);
			curr += pos;
			if (intree[tmp]) curr -= tmp;
		} else if (size == tsize) {
			int nxtp = ST.query(a[pos].dfn, a[pos].rit);
			int nxtt = ST.query(a[tmp].dfn, a[tmp].rit);
			if (nxtp >= nxtt) {
				Light.modify(a[pos].dfn, -1);
				Light.modify(a[tmp].dfn, 1);
				curr += pos;
				curr -= tmp;
			}
		}
	} else {
		int tmp = a[f].lc, tsize = Size.query(a[tmp].dfn, a[tmp].rit);
		if (!intree[tmp] || size > tsize) {
			Light.modify(a[pos].dfn, -1);
			if (intree[tmp]) Light.modify(a[tmp].dfn, 1);
			curr += pos;
			if (intree[tmp]) curr -= tmp;
		} else if (size == tsize) {
			int nxtp = ST.query(a[pos].dfn, a[pos].rit);
			int nxtt = ST.query(a[tmp].dfn, a[tmp].rit);
			if (nxtp > nxtt) {
				Light.modify(a[pos].dfn, -1);
				Light.modify(a[tmp].dfn, 1);
				curr += pos;
				curr -= tmp;
			}
		}
	}
} 
void work(int pos, int up) {
	a[pos].up = up;
	a[pos].dfn = ++timer;
	if (a[pos].lc == 0 && a[pos].rc != 0) work(a[pos].rc, up);
	if (a[pos].rc == 0 && a[pos].lc != 0) work(a[pos].lc, up);
	if (a[pos].lc != 0 && a[pos].rc != 0) {
		if (a[a[pos].lc].size >= a[a[pos].rc].size) {
			work(a[pos].lc, up);
			work(a[pos].rc, a[pos].rc);
		} else {
			work(a[pos].rc, up);
			work(a[pos].lc, a[pos].lc);
		}
	}
	a[pos].rit = timer;
}
void dfs(int pos) {
	a[pos].size = 1;
	if (a[pos].lc) {
		dfs(a[pos].lc);
		a[pos].size += a[a[pos].lc].size;
	}
	if (a[pos].rc) {
		dfs(a[pos].rc);
		a[pos].size += a[a[pos].rc].size;
	}
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		read(a[i].lc), read(a[i].rc);
		if (a[i].lc != 0) a[a[i].lc].father = i;
		if (a[i].rc != 0) a[a[i].rc].father = i;
	}
	dfs(1);
	work(1, 1);
	ST.init(n);
	Size.init(n);
	Light.init(n);
	for (int i = 1; i <= n; i++)
		home[a[i].dfn] = i;
	read(q);
	for (int i = 1; i <= q; i++) {
		read(pos[i]);
		vis[pos[i]] = true;
	}
	int point = q;
	for (int i = n; i >= 2; i--)
		if (!vis[home[i]]) pos[++point] = home[i];
	Size.modify(a[1].dfn, 1);
	intree[1] = true;
	for (int i = 1; i <= point; i++)
		ST.modify(a[pos[i]].dfn, i);
	for (int i = point; i >= 1; i--) {
		int now = pos[i];
		intree[now] = true;
		ST.modify(a[now].dfn, 0);
		Size.modify(a[now].dfn, 1);
		Light.modify(a[now].dfn, 1);
		renew(now); now = a[now].father;
		while (now != 0) {
			int tmp = Light.pred(a[now].dfn, Light.query(1, a[now].dfn));
			if (tmp >= a[a[now].up].dfn) {
				now = home[tmp];
				renew(now);
				now = a[now].father;
			} else now = a[a[now].up].father;
		}
		ans[i] = curr;
	}
	for (int i = 1; i <= q + 1; i++)
		printf("%lld\n", ans[i]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值