【ybtoj高效进阶 21280】景点距离(DP)(换根)

景点距离

题目链接:ybtoj高效进阶 21280

题目大意

给你一个线段树结构的树,要你支持一下操作:
删除一条边,或者判断当前有多少对点仍然连通。

思路

既然是树,我们考虑通过树来搞,而且还是线段树,就告诉我们它的深度是 \(\log\) 级别的,可以每次枚举。

而且!我们发现 \(k\) 最大 \(40\),可以只用维护 \(1\sim 40\) 的答案。
那你考虑一开始的答案是可以直接 \(1600n\) 的复杂度暴力求出。
然后求的过程中自然是 DP,自然要设 \(f_{i,j}\) 为 \(i\) 的子树内有多少个点到 \(i\) 路径为 \(j\)。不过 \(k\) 只有 \(40\),所以第二维只用开到 \(40\)。

接着考虑修改的贡献。
你修改了之后,你把那条跑找到,它两段为根,求出对于的 \(f\)了,那想上面一样匹配一下,就是减小的贡献数。
那在下面的那个还好,它就相当于它子树的根,但上面的那个就不行。
不过我们换根 DP 一下,移一下去,修改完之后移上来就可以了。

代码

#include<cstdio>
#include<cstring>
#define ll long long
#define rr register

using namespace std;

int n, m, x, rre;
ll a[200005][41];
ll ans[41], answer;
bool kl[200005];
char op, c;

int read() {
	rre = 0;
	c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		rre = (rre << 3) + (rre << 1) + c - '0';
		c = getchar();
	}
	return rre;
}

void write(ll x) {
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

void dfs(int now) {//dfs 求出以 1 为根的
	if (now > n) return ;
	
	dfs(now << 1); dfs(now << 1 | 1);
	a[now][0] = 1;
	for (int i = 1; i <= 40; i++)
		a[now][i] = a[now << 1][i - 1] + a[now << 1 | 1][i - 1];
	
	for (int i = 1; i <= 40; i++)
		ans[i] += a[now][i];
	for (int i = 0; i <= 38; i++)
		for (int j = 0; i + j <= 38; j++)
			ans[i + j + 2] += a[now << 1][i] * a[now << 1 | 1][j];
}

void up(int now, int fr) {//更新影响
	if (!now) return ;
	if (kl[fr]) return ;
	memset(a[now], 0, sizeof(a[now]));
	a[now][0] = 1;
	if (!kl[now << 1] && !kl[now << 1 | 1]) {
		for (int i = 1; i <= 40; i++)
			a[now][i] = a[now << 1][i - 1] + a[now << 1 | 1][i - 1];
	}
	else if (!kl[now << 1]) {
		for (int i = 1; i <= 40; i++)
			a[now][i] = a[now << 1][i - 1];
	}
	else if (!kl[now << 1 | 1]) {
		for (int i = 1; i <= 40; i++)
			a[now][i] = a[now << 1 | 1][i - 1];
	}
	up(now >> 1, now);
}

void down_rt(int now, int fr) {//换根
	if (!now) return ;
	if (kl[fr]) return ;
	down_rt(now >> 1, now);
	for (int i = 1; i <= 40; i++)
		a[now][i] -= a[fr][i - 1];
	for (int i = 1; i <= 40; i++)
		a[fr][i] += a[now][i - 1];
}

void up_rt(int now, int fr) {
	if (!now) return ;
	if (kl[fr]) return ;
	for (int i = 1; i <= 40; i++)
		a[fr][i] -= a[now][i - 1];
	for (int i = 1; i <= 40; i++)
		a[now][i] += a[fr][i - 1];
	up_rt(now >> 1, now);
}

int main() {
//	freopen("dis.in", "r", stdin);
//	freopen("dis.out", "w", stdout);
	
	n = read(); m = read();
	
	dfs(1);
	
	while (m--) {
		op = getchar();
		while (op != '-' && op != '?') op = getchar();
		
		x = read();
		if (op == '?') {
			answer = 0;
			for (int i = 1; i <= x; i++)
				answer += ans[i];
			write(answer); putchar('\n');
		}
		if (op == '-') {
			if (kl[x ^ 1]) {
				memset(a[x >> 1], 0, sizeof(a[x >> 1]));
				a[x >> 1][0] = 1;
			}
			else {
				a[x >> 1][0] = 1;
				for (int i = 1; i <= 40; i++)
					a[x >> 1][i] = a[x ^ 1][i - 1];
			}
			up(x >> 2, x >> 1);
			
			down_rt(x >> 2, x >> 1);
			
			for (int i = 0; i <= 39; i++)//减去贡献
				for (int j = 0; i + j <= 39; j++)
					ans[i + j + 1] -= a[x][i] * a[x >> 1][j];
			
			up_rt(x >> 2, x >> 1);
			
			kl[x] = 1;
		}
	}
	
	return 0;
}
上一篇:算法第三章上机实验报告


下一篇:“21天好习惯”第一期-1