[HNOI2015]开店 主席树+树链剖分

Description
给你一棵带边权、带点权的树。
每次询问所有点权为l~r的节点到节点u的距离之和


Sample Input
10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4


Sample Output
1603
957
7161
9466
3232
5223
1879
1669
1282
0


这道题真是太巧妙了。。。
我们把模型简化一下变成所有节点到节点u的距离,然后可以想到树上两点距离公式:
dep(x,y)=dep(1,x)+dep(1,y)-dep(lca)*2。
这个dep(1,x)+dep(1,y)这一部分我们其实很容易可以求出来,然后思考dep(lca)这一段,然后可以想到 [LNOI2014]LCA那道题。我们把树上每个点到根节点的节点全部+1,那么树上每个节点跟u的LCA的距离和就是:节点u到根节点的点的点权*这个点的距离,这个东西我们考虑树链剖分来维护。好吧,接下来就是怎么求一段点权的情况,这个很容易想到主席树,那你就排一下序,离散化一下,按顺序插链就好。
这里要注意一个点,可持久化线段树的lazy是不下传的,因为在可持久化线段树中有很多公用的节点,如果你在当前query的时态里下传了标记,那么其他时态与这个时态公用的节点也会受到影响,那其他时态就会多出当前query时态的影响,所以就不可以下传的。


#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int N = 160000;

struct trnode {
    int lc, rc;
    LL c, lazy;
} t[75 * N]; int cnt, rt[N];
struct edge {
    int x, y, d, next;
} e[2 * N]; int len, last[N];
struct node {
    int x, id;
} a[N];
int n, id, dep[N], tot[N], son[N], fa[N], ys[N], top[N];
LL sum[N], num[N], s1[N], s2[N], dis[N];
int cl[N];

bool cmp(node aa, node bb) {return aa.x < bb.x;}

void ins(int x, int y, int d) {
    e[++len].x = x; e[len].y = y; e[len].d = d;
    e[len].next = last[x]; last[x] = len;
}

void Link(int &u, int l, int r, int ll, int rr) {
    if(!u) u = ++cnt;
    t[u].c += sum[rr] - sum[ll - 1];
    if(l == ll && r == rr) {t[u].lazy++; return;}
    int mid = (l + r) / 2;
    if(rr <= mid) Link(t[u].lc, l, mid, ll, rr);
    else if(ll > mid) Link(t[u].rc, mid + 1, r, ll, rr);
    else Link(t[u].lc, l, mid, ll, mid), Link(t[u].rc, mid + 1, r, mid + 1, rr);
}

void Merge(int &u1, int u2) {
    if(!u1 || !u2) {u1 = u1 + u2; return;}
    t[u1].c += t[u2].c; t[u1].lazy += t[u2].lazy;
    Merge(t[u1].lc, t[u2].lc);
    Merge(t[u1].rc, t[u2].rc);
}

LL findsum(int u1, int u2, int l, int r, int ll, int rr, LL lazy) {
    if(l == ll && r == rr) return t[u2].c - t[u1].c + (sum[r] - sum[l - 1]) * lazy;
    int mid = (l + r) / 2; LL o = t[u2].lazy - t[u1].lazy;
    if(rr <= mid) return findsum(t[u1].lc, t[u2].lc, l, mid, ll, rr, lazy + o);
    else if(ll > mid) return findsum(t[u1].rc, t[u2].rc, mid + 1, r, ll, rr, lazy + o);
    else return findsum(t[u1].lc, t[u2].lc, l, mid, ll, mid, lazy + o)
     + findsum(t[u1].rc, t[u2].rc, mid + 1, r, mid + 1, rr, lazy + o);
}

void pre_tree_node(int x) {
    tot[x] = 1; son[x] = 0;
    for(int k = last[x]; k; k = e[k].next) {
        int y = e[k].y;
        if(y != fa[x]) {
            fa[y] = x;
            dep[y] = dep[x] + 1;
            pre_tree_node(y);
            tot[x] += tot[y];
            if(tot[son[x]] < tot[y]) son[x] = y;
        }
    }
}

void pre_tree_edge(int x, int tp, LL dd, int d) {
    ys[x] = ++id; top[x] = tp;
    num[id] = d; dis[x] = dd;
    s2[cl[x]] += dis[x];
    int gg;
    for(int k = last[x]; k; k = e[k].next) {
        int y = e[k].y;
        if(son[x] == y) gg = e[k].d;
    }
    if(son[x]) pre_tree_edge(son[x], tp, gg + dd, gg);
    for(int k = last[x]; k; k = e[k].next) {
        int y = e[k].y;
        if(fa[x] != y && son[x] != y) {
            pre_tree_edge(y, y, dd + e[k].d, e[k].d);
        }
    }
}

void change(int x, int y, int now) {
    int tx = top[x], ty = top[y];
    while(tx != ty) {
        if(dep[tx] > dep[ty]) swap(tx, ty), swap(x, y);
        Link(now, 1, n, ys[ty], ys[y]);
        y = fa[ty]; ty = top[y];
    }
    if(x == y) return;
    if(dep[x] > dep[y]) swap(x, y);
    Link(now, 1, n, ys[son[x]], ys[y]);
}

int erfen1(int x) {
    int l = 1, r = n, ans = 0;
    while(l <= r) {
        int mid = (l + r) / 2;
        if(a[mid].x <= x) l = mid + 1, ans = mid;
        else r = mid - 1;
    } return cl[a[ans].id];
}
int erfen2(int x) {
    int l = 1, r = n, ans = n + 1;
    while(l <= r) {
        int mid = (l + r) / 2;
        if(a[mid].x >= x) r = mid - 1, ans = mid;
        else l = mid + 1;
    } return cl[a[ans].id];
}

LL solve(int u, int x, int y) {
    x = erfen2(x), y = erfen1(y);
    if(x > y) return 0;
    int xx = x, yy = y; LL ans = 0;
    int rtx = rt[x - 1], rty = rt[y];
    x = 1; y = u; int tx = top[x], ty = top[y];
    while(tx != ty) {
        if(dep[tx] > dep[ty]) swap(tx, ty), swap(x, y);
        ans += findsum(rtx, rty, 1, n, ys[ty], ys[y], 0);
        y = fa[ty]; ty = top[y];
    }
    if(x == y) return (s1[yy] - s1[xx - 1]) * dis[u] + s2[yy] - s2[xx - 1] - 2LL * ans;
    if(dep[x] > dep[y]) swap(x, y);
    ans += findsum(rtx, rty, 1, n, ys[x], ys[y], 0);
    return (s1[yy] - s1[xx - 1]) * dis[u] + s2[yy] - s2[xx - 1] - 2LL * ans;
}

int main() {
    int m; LL A; scanf("%d%d%lld", &n, &m, &A);
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i].x), a[i].id = i;
    sort(a + 1, a + n + 1, cmp);
    int kk = 0; a[0].x = -1;
    for(int i = 1; i <= n; ++i) {
        if(a[i].x != a[i - 1].x) kk++;
        cl[a[i].id] = kk;
    }
    for(int i = 1; i < n; ++i) {
        int x, y, d; scanf("%d%d%d", &x, &y, &d);
        ins(x, y, d), ins(y, x, d);
    }
    for(int i = 1; i <= n; ++i) s1[cl[i]]++;
    pre_tree_node(1);
    pre_tree_edge(1, 1, 0, 0);
    for(int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + (LL)num[i], s1[i] += s1[i - 1], s2[i] += s2[i - 1];
    kk = 1; a[0].id = 0, a[n + 1].id = n + 1; cl[0] = 0, cl[n + 1] = kk + 1;
    for(int i = 1; i <= n; ++i) {
        rt[kk] = ++cnt;
        change(1, a[i].id, rt[kk]);
        while(i < n && a[i].x == a[i + 1].x) change(1, a[++i].id, rt[kk]);
        Merge(rt[kk], rt[kk - 1]);
        kk++;
    }
    LL lastans = 0;
    for(int i = 1; i <= m; ++i) {
        int x; LL a, b; scanf("%d%lld%lld", &x, &a, &b);
        int l = (a + lastans) % A, r = (b + lastans) % A;
        if(l > r) swap(l, r); lastans = solve(x, l, r);
        printf("%lld\n", lastans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值