@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,因为保证了父亲编号小于当前点的编号,相当于就给出了一个拓扑序。直接在拓扑序上搞就可以了。
奈何我眼睛比较不好,写博客的时候才突然发现有这样一个条件。
然后就是注意一下两个标记谁先谁后,显然乘法优先度是高于加法优先度的。