【ybt金牌导航4-1-4】【luogu P3261】城池攻占

城池攻占

题目链接:ybt金牌导航4-1-4 / luogu P3261

题目大意

给你一个有根树,然后点有权值,和改变系数。
改变系数可能是乘是个正数,也可能是加一个数。
然后又一些人,它们初始的属性值和位置已经确定,它们要往上跳,属性值如果小于当前点权就停下,否则就根据当前点改变系数改变,然后往上跳,直到停止或跳到根节点的上面。
问你每个人能跳多少次,有多少个人会在这个点停下。

思路

容易想到由于你修改属性值要么叫一个数,要么乘一个正数,那无论怎么修改,人们属性值的大小关系时不变的。

那我们考虑搞一个堆,然后每次看要不要杀死就一直拿出堆顶,判断它的权值,要杀死就杀死,并记录相关的答案。
然后你发现每个点都代表一个堆,然后你可以看到某个点的堆可以有它的儿子的堆和从它出发的点合并得到,自然想到左偏树。

然后接着问题就变成了如何维护左偏树里面数的值。
你考虑想搞线段树一样,用一个 lazy 懒标记来搞。
当你要合并的时候,你合并要用到两个左偏树的儿子的值,所以你这个时候把两个左偏树的标记都下传。
当你要删左偏树最上面的点的时候,它下面的信息还没有更新,直接删就丢掉了,所以要下传了之后再删。

至于下传,跟线段树其实是一样的。
为什么可以用懒标记呢,因为你每次需要的只是左偏树的根节点,那懒标记就是只求出根节点,你要下面的点的时候再往下传。

主要是具体实现比较烦,看看代码就行了。
(不开 long long 见祖宗)

代码

#include<queue>
#include<cstdio>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	int to, nxt;
}e[600001];
struct left_tree {
	int l, r, dy, dis, st;
	ll x, add, times;
}a[300001];
int n, m, fa[300001], fir[300001];
ll h[300001], v[300001], s[300001];
int op[300001], le[300001], KK, tot;
int killnum[300001], deg[300001], runroad[300001];
int root[300001];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

void down(int x) {//向下传懒标记
	if (a[x].l) {
		a[a[x].l].times *= a[x].times;
		a[a[x].l].add *= a[x].times;
		a[a[x].l].add += a[x].add;
		a[a[x].l].x = a[a[x].l].x * a[x].times + a[x].add;
	}
	if (a[x].r) {
		a[a[x].r].times *= a[x].times;
		a[a[x].r].add *= a[x].times;
		a[a[x].r].add += a[x].add;
		a[a[x].r].x = a[a[x].r].x * a[x].times + a[x].add;
	}
	a[x].times = 1;
	a[x].add = 0;
}

int merge(int x, int y) {
	if (!x) return y;
	if (!y) return x;
	
	down(x);//合并要用到点儿子的信息,所以要向下传递
	down(y);
	
	if (a[x].x > a[y].x) swap(x, y);
	a[x].r = merge(a[x].r, y);
	if (a[a[x].l].dis < a[a[x].r].dis) swap(a[x].l, a[x].r);
	a[x].dis = a[a[x].r].dis + 1;
	
	return x;
}

int delete_top(int now) {
	return merge(a[now].l, a[now].r);
}

void dfs(int now, int father) {
	deg[now] = deg[father] + 1;
	
	for (int i = le[now]; i; i = e[i].nxt)
		if (e[i].to != father) {
			dfs(e[i].to, now);
			root[now] = merge(root[now], root[e[i].to]);//先全部合并在一起
		}
	
	while (root[now] && a[root[now]].x < h[now]) {//再把要杀死的杀死
		down(root[now]);
		killnum[now]++;
		runroad[a[root[now]].dy] = deg[a[root[now]].st] - deg[now];//计算深度(你开始跳的深度-你停下的深度)
		root[now] = delete_top(root[now]);
	}
	
	if (op[now]) {//修改属性值(只用修改根节点和懒标记)
		a[root[now]].times *= v[now];
		a[root[now]].add *= v[now];
		a[root[now]].x *= v[now];
	}
	else {
		a[root[now]].add += v[now];
		a[root[now]].x += v[now];
	}
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%lld", &h[i]);
	for (int i = 2; i <= n; i++) {
		scanf("%d %d %lld", &fa[i], &op[i], &v[i]);
		add(i, fa[i]);
	}
	for (int i = 1; i <= m; i++) {
		scanf("%lld %d", &s[i], &fir[i]);
		a[i].st = fir[i];
		a[i].x = s[i];
		a[i].times = 1;
		a[i].dy = i;
		root[fir[i]] = merge(root[fir[i]], i);//把在同一个点出发的人都弄进一个树中
	}
	
	dfs(1, 0);
	
	while (root[1]) {//最后还有没有被杀死的人
		down(root[1]);
		runroad[a[root[1]].dy] = deg[a[root[1]].st];//那跳的次数就是它出发时的深度
		root[1] = delete_top(root[1]);
	}
	
	for (int i = 1; i <= n; i++)
		printf("%d\n", killnum[i]);
	for (int i = 1; i <= m; i++)
		printf("%d\n", runroad[i]);
	
	return 0;
}
posted @ 2021-05-21 17:07  あおいSakura  阅读(34)  评论(0编辑  收藏  举报