bzoj - 4003 城池攻占 【可并堆 + lazy标记】 好题!!!

传送门
题目大意:

小铭铭最近获得了一副新的桌游,游戏中需要用 m 个骑士攻占 n 个城池。
这 n 个城池用 1 到 n 的整数表示。除 1 号城池外,城池 i 会受到另一座城池 fi 的管辖,
其中 fi < i。也就是说,所有城池构成了一棵有根树。这 m 个骑士用 1 到 m 的整数表示,其
中第 i 个骑士的初始战斗力为 si,第一个攻击的城池为 ci。
每个城池有一个防御值 hi,如果一个骑士的战斗力大于等于城池的生命值,那么骑士就可
以占领这座城池;否则占领失败,骑士将在这座城池牺牲。占领一个城池以后,骑士的战斗力
将发生变化,然后继续攻击管辖这座城池的城池,直到占领 1 号城池,或牺牲为止。
除 1 号城池外,每个城池 i 会给出一个战斗力变化参数 ai;vi。若 ai =0,攻占城池 i 以后骑士战斗力会增加 vi;若 ai =1,攻占城池 i 以后,战斗力会乘以 vi。注意每个骑士是单独计算的。也就是说一个骑士攻击一座城池,不管结果如何,均不会影响其他骑士攻击这座城池的结果.
现在的问题是, 对于每个城池, 输出有多少个骑士在这里牺牲; 对于每个骑士, 输出他攻占的城池数量

这道题算是可并堆中最难的一道了把.. 注意左偏树也是一颗二叉树, 所以是满足标记下推的, 所以对于乘的和加的打上相应的标记即可, 然后即使下放的时候有两个, 一个是当前点要被删除时需要提前下推一下, 而是进行合并的时候要在左右儿子有新编号的时候下推一下. 还有注意的点就是乘和加标记的先后顺序关系, 线段树中的双标记下推规则就不细说了. 这里的root不能初始化为自己, 因为这个跟自己没有任何关系, 所以root[i] == 0 的就不用进行判断了.. 细节请看代码

AC Code

const int maxn = 3e5 + 5;
int root[maxn];
struct Ltree {
    int ls[maxn<<1], rs[maxn<<1]; // 左右儿子的编号
    ll val[maxn<<1], mul[maxn<<1], add[maxn<<1];
    // 该点的值, 改点的+/*的标记, 因为左偏树也是二叉树
    // 所以可以想线段树那样进行下放
    void funmul(int id, ll tmp) {
        val[id] *= tmp; mul[id] *= tmp;
        add[id] *= tmp;
    }
    void funadd(int id, ll tmp) {
        val[id] += tmp; add[id] += tmp;
    }
    void pushdown(int id) {
        if (mul[id] != 1) {
            funmul(ls[id], mul[id]);
            funmul(rs[id], mul[id]);
            mul[id] = 1;
        }
        if (add[id]) {
            funadd(ls[id], add[id]);
            funadd(rs[id], add[id]);
            add[id] = 0;
        }
    }
    int Un(int x, int y){  // 小根堆/ 看情况维护, 改个符号就行
        if (!x || !y) return x+y;
        if (val[x] > val[y]) swap(x,y);
        pushdown(x);
        rs[x] = Un(rs[x], y);
        swap(ls[x], rs[x]);
        return x;
    }
    void pop(int &x) {
        x = Un(ls[x], rs[x]);
    }
    int top(int x) {
        return val[x];
    }
}heap;
int a[maxn];
ll h[maxn], v[maxn], ci[maxn];
int ans[maxn], siz[maxn], dep[maxn];
int n, m;
struct node {
    int to, next, w;
}e[maxn];
int cnt, head[maxn];
void init() {
    cnt = 0; Fill(head, -1);
}
void add(int u, int v, int w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt++;
}
void dfs(int u, int fa, int d) {
    dep[u] = d + 1;
    for (int i = head[u] ; ~i ; i = e[i].next) {
        int to = e[i].to;
        dfs(to, u, d + 1);
        root[u] = heap.Un(root[u], root[to]);
    }
    if (!root[u]) return ;
    int rf = root[u];
    while (heap.val[rf] < h[u]) {
        heap.pushdown(rf);
        ++ siz[u];
        ans[rf] = dep[ci[rf]] - dep[u];
        heap.pop(root[u]);
        if (!root[u]) return ;
        rf = root[u];
    }
    if (a[u]) heap.funmul(rf, v[u]);
    else heap.funadd(rf, v[u]);
}
void solve() {
    scanf("%d%d", &n, &m); init();
    for (int i = 1 ; i <= n ; i ++) scanf("%lld", h+i);
    for (int i = 2 ; i <= n ; i ++) {
        int x;
        scanf("%d%d%lld", &x, &a[i], &v[i]);
        add(x, i, 1);
    }
    for (int i = 1 ; i <= m ; i ++) {
        ll x;
        scanf("%lld%d", &x, ci+i);
        heap.val[i] = x; heap.mul[i] = 1;
        root[ci[i]] = heap.Un(root[ci[i]], i);
    }
    Fill(ans, -1); dfs(1, -1, 0);
    for (int i = 1; i <= n ; i ++) printf("%d\n", siz[i]);
    for (int i = 1; i <= m ; i ++) {
        printf("%d\n", ans[i] == -1 ? dep[ci[i]]: ans[i]);
    }
}

你可能感兴趣的:(可并堆/左偏树)