[LuoguP5305][GXOI/GZOI2019]旧词 (树链剖分)

[GXOI/GZOI2019]旧词 (树链剖分)

题面

给定一棵 \(n\)个点的有根树,节点标号 \([1,n]\),1号节点为根。
给定常数\(k\)
给定\(Q\)个询问,每次询问给定\(x,y\),求:\(\sum_{i=1}^x \mathrm{deep}(\mathrm{lca}(i,y)) \mod 998244353\)

分析

此题为[BZOJ3626] [LNOI2014]LCA(树链剖分)的加强版。

考虑原来的做法(k=1):我们把i到根的路径上所有点+1,y到根路径上的权值和就是lca深度。如果有多个点i,那么权值和就是深度之和。离线再线段树维护即可。

如果把1改成k,类比原来把\(deep[i]\)拆成\(deep[i]\)个1,我们可以把\(deep[i]^k\)拆成\(deep[i]^{k}-(deep[i]-1)^k,(deep[i]-1)^k-(deep[i]-2)^k \dots\)等.这样问题就变成了用线段树维护一个序列\(s\)支持:

  1. 对\(i \in [l,r]\),\(s_i\)加上定值\(val[i]=deep[i]^k-(deep[i]-1)^k\)
  2. 求\([l,r]\)的区间和

每个线段树区间维护一个变量delta记录区间内val之和,可以在建树的时候求出。

然后用一个懒标记addm记录每个区间被整体增加的次数,下推的时候加上addm*对应的val之和

 代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define maxn 200000
#define mod 998244353
using namespace std;
typedef long long ll;
inline void qread(int &x) {
    x=0;
    int sign=1;
    char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    x=x*sign;
}
inline void qprint(ll x) {
    if(x<0) {
        putchar('-');
        qprint(-x);
    } else if(x==0) {
        putchar('0');
        return;
    } else {
        if(x>=10) qprint(x/10);
        putchar('0'+x%10);
    }
}

inline ll fast_pow(ll x,ll k) {
    ll ans=1;
    while(k) {
        if(k&1) ans=ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}

int n,m,k;
struct edge {
    int from;
    int to;
    int next;
} E[maxn*2+5];
int esz=1;
int head[maxn+5];
void add_edge(int u,int v) {
    esz++;
    E[esz].from=u;
    E[esz].to=v;
    E[esz].next=head[u];
    head[u]=esz;
}

int deep[maxn+5],sz[maxn+5],fa[maxn+5],son[maxn+5],top[maxn+5],dfn[maxn+5],hash_dfn[maxn+5];
void dfs1(int x,int f) {
    fa[x]=f;
    sz[x]=1;
    deep[x]=deep[f]+1;
    for(int i=head[x]; i; i=E[i].next) {
        int y=E[i].to;
        if(y!=f) {
            dfs1(y,x);
            sz[x]+=sz[y];
            if(sz[son[x]]<sz[y]) son[x]=y;
        }
    }
}
int tim;
void dfs2(int x,int t) {
    dfn[x]=++tim;
    hash_dfn[dfn[x]]=x;
    top[x]=t;
    if(son[x]) dfs2(son[x],t);
    for(int i=head[x]; i; i=E[i].next) {
        int y=E[i].to;
        if(y!=fa[x]&&y!=son[x]) {
            dfs2(y,y);
        }
    }
}
int lca(int x,int y) {
    while(top[x]!=top[y]) {
        if(deep[top[x]]>deep[top[y]]) x=fa[top[x]];
        else y=fa[top[y]];
    }
    if(deep[x]<deep[y]) return x;
    else return y;
}

struct query {
    int x;
    int y;
    ll ans;
    friend bool operator < (query p,query q) {
        return p.x<q.x;
    }
} q[maxn+5];
vector<int>id[maxn+5];
ll val[maxn+5];//预处理每个点的贡献
struct segment_tree {
    struct node {
        int l;
        int r;
        ll sum;
        ll delta;//每次计算要加上的贡献,等于sum(val[i]) (l<=i<=r)
        ll addm;//记录每个区间的贡献被计算的次数
        int len() {
            return r-l+1;
        }
    } tree[maxn*4+5];
    void push_up(int pos) {
        tree[pos].sum=(tree[pos<<1].sum+tree[pos<<1|1].sum)%mod;
        tree[pos].delta=(tree[pos<<1].delta+tree[pos<<1|1].delta)%mod;
    }
    void build(int l,int r,ll *a,int pos) {
        tree[pos].l=l;
        tree[pos].r=r;
        if(l==r){
            tree[pos].delta=a[hash_dfn[l]];
            return;
        }
        int mid=(l+r)>>1;
        build(l,mid,a,pos<<1);
        build(mid+1,r,a,pos<<1|1);
        push_up(pos);
    }
    void add_tag(int x,int mark) {
        tree[x].sum+=mark*tree[x].delta%mod;
        tree[x].sum%=mod;
        tree[x].addm+=mark;
        tree[x].addm%=mod;
    }
    void push_down(int x) {
        if(tree[x].addm) {
            add_tag(x<<1,tree[x].addm);
            add_tag(x<<1|1,tree[x].addm);
            tree[x].addm=0;
        }
    }
    void update(int L,int R,int val,int pos) {
        if(L<=tree[pos].l&&R>=tree[pos].r) {
            add_tag(pos,val);
            return;
        }
        push_down(pos);
        int mid=(tree[pos].l+tree[pos].r)>>1;
        if(L<=mid) update(L,R,val,pos<<1);
        if(R>mid) update(L,R,val,pos<<1|1);
        push_up(pos);
    }
    ll query(int L,int R,int pos) {
        if(L<=tree[pos].l&&R>=tree[pos].r) {
            return tree[pos].sum;
        }
        push_down(pos);
        int mid=(tree[pos].l+tree[pos].r)>>1;
        ll ans=0;
        if(L<=mid) ans+=query(L,R,pos<<1);
        if(R>mid) ans+=query(L,R,pos<<1|1);
        return ans;
    }
} T;

void update(int x,int y) {
    int tx=top[x],ty=top[y];
    while(tx!=ty) {
        if(deep[tx]<deep[ty]) {
            swap(x,y);
            swap(tx,ty);
        }
        T.update(dfn[tx],dfn[x],1,1);
        x=fa[tx];
        tx=top[x];
    }
    if(deep[x]>deep[y]) swap(x,y);
    T.update(dfn[x],dfn[y],1,1);
}
ll query(int x,int y) {
    ll ans=0;
    int tx=top[x],ty=top[y];
    while(tx!=ty) {
        if(deep[tx]<deep[ty]) {
            swap(x,y);
            swap(tx,ty);
        }
        ans+=T.query(dfn[tx],dfn[x],1);
        x=fa[tx];
        tx=top[x];
    }
    if(deep[x]>deep[y]) swap(x,y);
    ans+=T.query(dfn[x],dfn[y],1);
    return ans;
}

int main() {
    int f;
    qread(n);
    qread(m);
    qread(k);
    for(int i=2; i<=n; i++) {
        qread(f);
        add_edge(i,f);
        add_edge(f,i);
    }
    dfs1(1,0);
    dfs2(1,1);
    for(int i=1; i<=m; i++) {
        qread(q[i].x);
        qread(q[i].y);
        id[q[i].x].push_back(i);
    }
    for(int i=1;i<=n;i++){
        val[i]=(fast_pow(deep[i],k)-fast_pow(deep[i]-1,k)+mod)%mod;
    }
    T.build(1,n,val,1);
    for(int i=1; i<=n; i++) {
        update(1,i);
        for(int j=0; j<(int)id[i].size(); j++) {
            int num=id[i][j];
            q[num].ans=query(1,q[num].y)%mod;
        }
    }
    for(int i=1; i<=m; i++) {
        qprint(q[i].ans);
        putchar('\n');
    }
}

/*
5 5 1
1
4
1
2
4 3
5 4
2 5
1 2
3 2
*/

原文地址:https://www.cnblogs.com/birchtree/p/12104131.html

时间: 2024-10-07 20:49:21

[LuoguP5305][GXOI/GZOI2019]旧词 (树链剖分)的相关文章

从lca到树链剖分 bestcoder round#45 1003

bestcoder round#45 1003 题,给定两个点,要我们求这两个点的树上路径所经过的点的权值是否出现过奇数次.如果是一般人,那么就是用lca求树上路径,然后判断是否出现过奇数次(用异或),高手就不这么做了,直接树链剖分.为什么不能用lca,因为如果有树退化成链,那么每次询问的复杂度是O(n), 那么q次询问的时间复杂度是O(qn) 什么是树链剖分呢? 就是把树的边分成轻链和重链 http://blogsina.com.cn/s/blog_6974c8b20100zc61.htmlh

HDU 5296 Annoying Problem 树链剖分 LCA 倍增法

HDU 5296 Annoying Problem 题目链接:hdu 5296 题意:在一棵给定的具有边权的树,一个节点的集合S(初始为空),给定Q个操作,每个操作增加或删除S中的一个点,每个操作之后输出使集合S中所有点联通的最小子树的边权和. 思路:最小子树上的节点的充要条件: 节点为(S集合中所有点的LCA)的子节点: 节点有一个子孙为S集合中的点. 那么我们给每个节点都开一个标记数组,初始为零,每加入一个节点,就把从这个节点到根节点路径上的点的值都+1,反之-1,这样通过对每个单节点值的查

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In

bzoj 2243: [SDOI2011]染色 线段树区间合并+树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 7925  Solved: 2975[Submit][Status][Discuss] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完

bzoj3694: 最短路(树链剖分/并查集)

bzoj1576的帮我们跑好最短路版本23333(双倍经验!嘿嘿嘿 这题可以用树链剖分或并查集写.树链剖分非常显然,并查集的写法比较妙,涨了个姿势,原来并查集的路径压缩还能这么用... 首先对于不在最短路径树上的边x->y,设t为最短路径树上lca(x,y),则t到y上的路径上的点i到根的距离都可以用h[x]+dis[x][y]+h[y]-h[i](h[]为深度)来更新,因为h[i]一定,只要让h[x]+dis[x][y]+h[y]最小就行,这里用树剖直接修改整条链上的数,就可以过了. 并查集的

洛谷 P3384 【模板】树链剖分

题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数N.M.R.P,分别表示树的结点个数.操作个数

bzoj1036 树的统计(树链剖分+线段树)

1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 15120  Solved: 6141[Submit][Status][Discuss] Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 I

SPOJ QTREE Query on a tree ——树链剖分 线段树

[题目分析] 垃圾vjudge又挂了. 树链剖分裸题. 垃圾spoj,交了好几次,基本没改动却过了. [代码](自带常数,是别人的2倍左右) #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 20005 int T,n,fr[maxn],h[maxn],to[maxn],ne[maxn]

树链剖分简(单)介(绍)

树链剖分可以算是一种数据结构(一大堆数组,按照这个意思,主席树就是一大堆线段树).将一棵树分割成许多条连续的树链,方便完成一下问题: 单点修改(dfs序可以完成) 求LCA(各种乱搞也可以) 树链修改(修改任意树上两点之间的唯一路径) 树链查询 (各种操作)  前两个内容可以用其他方式解决,但是下面两种操作倍增.st表,dfs序就很难解决(解决当然可以解决,只是耗时长点而已).下面开始步入正题. 树链剖分的主要目的是分割树,使它成一条链,然后交给其他数据结构(如线段树,Splay)来进行维护.常