[JLOI2015] 城池攻占
左偏树以及懒标记的应用。
Desprition
每个有初始战斗力和出发点的士兵,在一棵有根树上在满足战斗力大于该点防御力的条件下不断沿节点父亲向上进攻。如果不满足条件,就阵亡在该节点。求对于每个城池,输出有多少个骑士在这里牺牲;对于每个骑士,输出他攻占的城池数量。
Solution
因为考虑到每个节点都有固定的防御值,而任意一个士兵的行动不会影响其他人且攻打同一个节点的士兵可以为多人。
所以我们考虑将所有当前攻打同一个点的士兵放进同一个组合里,按从小到大的顺序排列,
如果其中最小的都满足大于等于当前节点防御值的条件,那就说明组合里所有士兵都能继续向上走,就将该组合的士兵与节点父亲的组合合并;
如果最小的不满足条件,说明他在这里死了,就存下他的死亡记录,将他踢出组合,继续判断,直到组合内为空时停止。
咦,注意到不断合并,删除,找最小值得操作了吗?
左偏树:那不就是本大爷的主场吗?!放开那道题,让我来!(
但是,题目中也说得很明白,每次攻打成功后,士兵的战斗力会发生变化。每次都修改所有士兵的值肯定是不现实的,所以我们需要使用懒标记处理。标记下传的操作其实跟线段树的差不多,这里就不细讲了。有一点要注意的就是,每次应该先修改乘法懒标记,再修改加法懒标记,而且如果当前变化是乘法,节点对应的加法标记也要修改哦~
所以,最后的答案,都是通过士兵的死亡地和出发地求得的。
-
有多少个骑士在这里牺牲?
只要循环一遍,在每个士兵的死亡地加一就好了。
-
每个士兵攻占的城池数量?
就是该士兵出发节点的深度减去死亡地的深度。很简单吧~
具体讲解都在代码里了~
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define il inline
#define int long long
const int N = 3e5 + 5;
int n,m,h[N],fa[N],a[N],v[N],c[N],rt[N],s[N],ls[N],rs[N],dis[N];
int dep[N],d[N],siz[N],tag[N],tag2[N];
il void read(int &x) {
x = 0; int f = 1; char s = getchar();
while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();}
while(s <= '9' && s >= '0') x = x * 10 + s - '0', s = getchar();
x *= f;
}
il void write(int x) {
if(x < 0) x = -x, putchar('-');
if(x > 9) write(x / 10), x %= 10;
putchar(x + '0');
}
il void pushdown(int x) {
if(!tag[x] && tag2[x] == 1) return;
if(ls[x]) {
s[ls[x]] *= tag2[x], s[ls[x]] += tag[x];
tag[ls[x]] *= tag2[x], tag[ls[x]] += tag[x];
tag2[ls[x]] *= tag2[x];
}
if(rs[x]) {
s[rs[x]] *= tag2[x], s[rs[x]] += tag[x];
tag[rs[x]] *= tag2[x], tag[rs[x]] += tag[x];
tag2[rs[x]] *= tag2[x];
}
tag[x] = 0, tag2[x] = 1;
}
il int merge(int x,int y) {
if(!x || !y) return x + y;
pushdown(x), pushdown(y);
if(s[x] > s[y]) swap(x,y);
rs[x] = merge(rs[x],y);
if(dis[rs[x]] > dis[ls[x]]) swap(ls[x],rs[x]);
dis[x] = dis[rs[x]] + 1;
return x;
}
signed main() {
int i;
read(n), read(m);
dep[1] = 1, dis[0] = -1;
for(i = 1; i <= n; i ++) read(h[i]), rt[i] = -1;
for(i = 2; i <= n; i ++) read(fa[i]), read(a[i]), read(v[i]), dep[i] = dep[fa[i]] + 1;//因为题中规定了f[i] < i, 所以在 fi的深度在i之前就已经确定下来了,可以直接算i的深度
for(i = 1; i <= m; i ++) {
tag2[i] = 1;
read(s[i]), read(c[i]);
if(~rt[c[i]]) rt[c[i]] = merge(rt[c[i]],i);//合并攻击同一节点的士兵
else rt[c[i]] = i;
}
for(i = n; i; i --) {
while(rt[i] != -1) {
if(s[rt[i]] < h[i]) {//如果打不下来
d[rt[i]] = i, pushdown(rt[i]);//该士兵阵亡,记得标记下传
if(!ls[rt[i]]) rt[i] = -1;/*左偏树的性质:重心向左,左边的节点数一定大于等于右边
因此,如果左边都没有了,右边肯定也没有,跳出。 */
else rt[i] = merge(ls[rt[i]],rs[rt[i]]);//否则,合并左右儿子,产生新的顶点代替当前顶点,继续比较
}
else break;//如果顶点满足条件,整棵子树都满足
}
if(i == 1) break;//特判,如果已经到树根,就无法再向上了
if(rt[i] == -1) continue;//如果为空了,直接跳出
if(!a[i]) s[rt[i]] += v[i], tag[rt[i]] += v[i];
else s[rt[i]] *= v[i], tag[rt[i]] *= v[i], tag2[rt[i]] *= v[i];
pushdown(rt[i]);
if(rt[fa[i]] == -1) rt[fa[i]] = rt[i];
else rt[fa[i]] = merge(rt[fa[i]],rt[i]);//整体上走
}
for(i = 1; i <= m; i ++) siz[d[i]] ++;
for(i = 1; i <= n; i ++) write(siz[i]), puts("");
for(i = 1; i <= m; i ++) write(dep[c[i]] - dep[d[i]]), puts("");
return 0;
}