@bzoj - 4003@ [JLOI2015]城池攻占


@description@

n 个城池构成一棵有根树,第 i 个城池的父亲为 fi(fi < i),防御值为 hi。

有 m 个骑士,第 i 个骑士的初始战斗力为 si,第一个攻击的城池为 ci。

如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可以占领这座城池;否则骑士将在这座城池牺牲。

占领一个城池以后,骑士的战斗力将发生变化,然后继续攻击这座城池的父亲,直到占领 1 号城池或牺牲为止。

除 1 号城池外,每个城池 i 会给出一个战斗力变化参数 ai,vi。若 ai =0,攻占城池 i 以后骑士战斗力会增加 vi;若 ai =1,攻占城池 i 以后,战斗力会乘以 vi,这个时候保证 vi > 0。

骑士之间不会互相影响的。

问对于每个城池,有多少个骑士在这里牺牲;对于每个骑士,他攻占的城池数量。

input
第 1 行包含两个正整数 n,m,表示城池的数量和骑士的数量。

第 2 行包含 n 个整数,其中第 i 个数为 hi,表示城池 i 的防御值。

第 3 到 n +1 行,每行包含三个整数。其中第 i +1 行的三个数为 fi,ai,vi,分别表示这座城池的父亲和两个战斗力变化参数。

第 n +2 到 n + m +1 行,每行包含两个整数。其中第 n + i 行的两个数为 si;ci,分别表示初始战斗力和第一个攻击的城池。

保证 1 <= n;m <= 300000, 1 <= fi<i; 1 <= ci <= n; -10^18 <= hi,vi,si <= 10^18;ai等于1或者2。且当 ai =1 时,vi > 0。

保证任何时候骑士战斗力值的绝对值不超过 10^18。

output
输出 n + m 行,每行包含一个非负整数。其中前 n 行分别表示在城池 1 到 n 牺牲的骑士数量,后 m 行分别表示骑士 1 到 m 攻占的城池数量。

sample input
5 5
50 20 10 10 30
1 1 2
2 0 5
2 0 -10
1 0 10
20 2
10 3
40 4
20 4
35 5
sample output
2
2
0
0
0
1
1
3
1
1

@solution@

首先题目这个输出格式很显然对在线算法不友好,并且这道题在线也不好做。我们考虑离线。

我们要离线处理什么呢?处理出每一个骑士在哪个点 die,这样题目中的两个输出要求都可以搞出来了。

离线有什么好处呢?对于某一个点,我们可以将这个点内所有的骑士一起处理。我们要判断哪些骑士会 die,哪些骑士进入它的父亲。
可以发现每个骑士只会 die 一次,因此我们直接暴力找到一个会 die 的骑士,然后把它弹走,再重复这个过程就可以了。

谁最容易 die 呢?自然是攻击力最小的那个。我们对于每一个结点建一个小根堆,弹出攻击力所有不符合要求的骑士,再将这个堆往上送给父亲。
父亲方面要将所有儿子的堆合并起来,所以使用左偏树。

还有一个问题,骑士的攻击力要发生改变。我们给堆顶打一个 tag,然后合并的时候将 tag 传下去就可以了。
因为加上一个数不会影响两个数的大小关系,乘上一个正数也不会影响两个数的大小关系。

@accepted code@

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 300000;
struct edge{
	edge *nxt; int to;
}edges[MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
	edge *p = (++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
}
struct node{
	node *ch[2];
	int dis, num;
	ll key, atag, mtag;
}pl[MAXN + 5], *rt[MAXN + 5], *NIL, *tcnt;
void pushdown(node *x) {
	if( x->mtag != 1 ) {
		if( x->ch[0] != NIL )
			x->ch[0]->atag *= x->mtag, x->ch[0]->mtag *= x->mtag, x->ch[0]->key *= x->mtag;
		if( x->ch[1] != NIL )
			x->ch[1]->atag *= x->mtag, x->ch[1]->mtag *= x->mtag, x->ch[1]->key *= x->mtag;
		x->mtag = 1;
	}
	if( x->atag ) {
		if( x->ch[0] != NIL )
			x->ch[0]->atag += x->atag, x->ch[0]->key += x->atag;
		if( x->ch[1] != NIL )
			x->ch[1]->atag += x->atag, x->ch[1]->key += x->atag;
		x->atag = 0;
	}
}
node *merge(node *a, node *b) {
	if( a == NIL ) return b;
	if( b == NIL ) return a;
	if( a->key > b->key ) swap(a, b);
	pushdown(a);
	a->ch[1] = merge(a->ch[1], b);
	if( a->ch[0]->dis < a->ch[1]->dis ) swap(a->ch[0], a->ch[1]);
	a->dis = a->ch[1]->dis + 1;
	return a;
}
node *newnode(ll x, int n) {
	tcnt++;
	tcnt->ch[0] = tcnt->ch[1] = NIL;
	tcnt->key = x, tcnt->num = n;
	tcnt->dis = 1, tcnt->atag = 0, tcnt->mtag = 1;
	return tcnt;
}
int dep[MAXN + 5], dfn[MAXN + 5], dcnt;
void init() {
	tcnt = NIL = &pl[0], NIL->dis = 0;
	for(int i=1;i<=MAXN;i++)
		rt[i] = NIL;
	dcnt = 0;
}
int a[MAXN + 5], dead[MAXN + 5], cnt[MAXN + 5];
ll h[MAXN + 5], v[MAXN + 5];
void dfs(int x) {
	for(edge *p=adj[x];p;p=p->nxt)
		dep[p->to] = dep[x] + 1, dfs(p->to), rt[x] = merge(rt[x], rt[p->to]);
	while( rt[x] != NIL && rt[x]->key < h[x] )
		dead[rt[x]->num] = x, pushdown(rt[x]), rt[x] = merge(rt[x]->ch[0], rt[x]->ch[1]);
	if( a[x] )
		rt[x]->atag *= v[x], rt[x]->mtag *= v[x], rt[x]->key *= v[x];
	else rt[x]->atag += v[x], rt[x]->key += v[x];
}
int c[MAXN + 5];
int main() {
	int n, m; init();
	scanf("%d%d", &n, &m);
	for(int i=1;i<=n;i++)
		scanf("%lld", &h[i]);
	for(int i=2;i<=n;i++) {
		int fa; scanf("%d%d%lld", &fa, &a[i], &v[i]);
		addedge(fa, i);
	}
	for(int i=1;i<=m;i++) {
		ll s; scanf("%lld%d", &s, &c[i]);
		rt[c[i]] = merge(rt[c[i]], newnode(s, i));
	}
	dep[1] = 1, dfs(1);
	for(int i=1;i<=m;i++) cnt[dead[i]]++;
	for(int i=1;i<=n;i++) printf("%d\n", cnt[i]);
	for(int i=1;i<=m;i++) printf("%d\n", dep[c[i]] - dep[dead[i]]);
}

@details@

压行好像压的有点儿厉害?希望大家还能看得懂。

其实并不需要 dfs,因为保证了父亲编号小于当前点的编号,相当于就给出了一个拓扑序。直接在拓扑序上搞就可以了。
奈何我眼睛比较不好,写博客的时候才突然发现有这样一个条件。

然后就是注意一下两个标记谁先谁后,显然乘法优先度是高于加法优先度的。

posted @ 2019-01-18 12:00  Tiw_Air_OAO  阅读(204)  评论(0编辑  收藏  举报