【树链剖分+线段树】Water Tree | CF 343D

树链剖分入门+线段树:Water Tree

【前置知识】

树链剖分(知识点太多了这里就不详细讲原理了!)
线段树 (知识点太多了这里就不详细讲原理了!)
这里就讲讲方法

【难度】

3.5 / 10 3.5/10 3.5/10
模板题

【题意】

给你一个有根树,根节点为 1 1 1
顶点编号从 1 1 1 n n n ,初始每个顶点都代表一个空的水库。

你有一下三种操作:

(1)给水库 v v v 充水。这样, v v v 和它所有的儿子的水库都会充满水。
(2)给水库 v v v 放水。这样, v v v 和它所有的祖先的水库都会变空。
(3)询问 水库 v v v 现在是充满水的还是放空的。

【数据范围】

顶点个数: 1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times10^5 1n5×105
查询次数: 1 ≤ q ≤ 5 × 1 0 5 1\le q\le 5\times10^5 1q5×105
保证给的是树。

【样例输入】

n n n 顶点数
n − 1 n-1 n1条边
q q q 查询次数
o p e r a t i o n i d operation\quad id operationid

5
1 2
5 1
2 3
4 2
12
1 1
2 3
3 1
3 2
3 3
3 4
1 2
2 4
3 1
3 3
3 4
3 5

【样例输出】

0
0
0
1
0
1
0
1

【思路】

【操作1的做法】

对于某个顶点 v v v ,我们易得该顶点的 d f s 序 \color{cyan}dfs序 dfs d f n [ v ] \pmb{dfn[v]} dfn[v]dfn[v]dfn[v] 子 树 大 小 \color{cyan}子树大小 S i z [ v ] \pmb{Siz[v]} Siz[v]Siz[v]Siz[v]
对 于 某 棵 d f s 树 的 顶 点 v , 它 及 它 子 树 的 d f s 序 是 连 续 的 区 间 管 理 范 围 为 [ d f n [ v ] , d f n [ v ] + S i z [ v ] − 1 ] \color{red}对于某棵dfs树的顶点v,它及它子树的dfs序是连续的\\ 区间管理范围为 [dfn[v],dfn[v]+Siz[v]-1] dfsvdfs[dfn[v],dfn[v]+Siz[v]1]
我们利用这个性质,加上 线 段 树 \color{green}线段树 线的遍历的区间加 / 区间求和 等操作进行处理。

【操作3的做法】

有了线段树,单点查询不是易如反掌?

【操作2的做法】

关键我们需要知道怎么修改该顶点到根节点的所有顶点置零操作。

对啦!就是通过 树 链 剖 分 \color{red}树链剖分 来快速到达根节点!(喂只有你才是这么写的吧。。)

我们分好轻重链后,可以快速 上 跳 \color{green}上跳 操作,来在 O ( log ⁡ N ) \color{red}O(\log N) O(logN) 的时间内到达根节点而不是暴力做法的 O ( N ) O(N) O(N)

然后对于每一段上跳的区间,我们进行线段树置零操作,所以时间复杂度为 O ( log ⁡ log ⁡ N ) \color{red}O(\log\log N) O(loglogN)

区间置零等于区间乘以 0 0 0呀!!
区间置 1 1 1 等于区间乘以 0 0 0 再加上 1 1 1 呀!!
使用有乘法和加法操作的线段树板子即可。(甚至可以不用乘法,不过看着不爽!)

【核心代码】

时间复杂度: O ( N + Q × N log ⁡ log ⁡ N ) O(N+Q\times N\log\log N) O(N+Q×NloglogN)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 6e5+50;
const ll  MOD = 998244353;

int Top[MAX],Siz[MAX],Dep[MAX],Fa[MAX],Son[MAX],dfn[MAX],nfd[MAX];
int a[MAX];
int tim;
int n,q;
vector<int>G[MAX];		/// 难道没人喜欢可爱的邻接表吗 o(' 3 `)o
				/// 线段树板子(太大了,我都卡了。)
#define mo MOD

struct tree
{
    int l,r;//代表节点维护的区间范围;
    ll data; //代表该节点维护的值;
    ll lazy_Add; //涉及加法lazy标记的东西;
    ll lazy_Mul; //涉及乘法lazy标记的东西;
}t[MAX << 2];

inline int lson(int p){return p << 1;}//左儿子;
inline int rson(int p){return p << 1 | 1;}//右儿子;

void build(int p,int l,int r)
{
    t[p].l = l,t[p].r = r,t[p].lazy_Mul = 1;//以p为编号的节点维护的区间为[l,r];
    if(l == r)//叶子节点存放真实的数值;
    {
        t[p].data = a[l] % mo;
        return;
    }
    int mid = t[p].l + t[p].r >> 1;
    build(lson(p),l,mid);
    build(rson(p),mid + 1,r);
    //回溯时将其子节点的信息存下来;
    t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}

void push_down(int p)
{
    t[lson(p)].data = (t[lson(p)].data * t[p].lazy_Mul % mo + (t[p].lazy_Add * (t[lson(p)].r - t[lson(p)].l + 1) % mo)) % mo;
    t[rson(p)].data = (t[rson(p)].data * t[p].lazy_Mul % mo + (t[p].lazy_Add * (t[rson(p)].r - t[rson(p)].l + 1) % mo)) % mo;
    t[lson(p)].lazy_Mul = t[lson(p)].lazy_Mul * t[p].lazy_Mul % mo;
    t[lson(p)].lazy_Add = (t[lson(p)].lazy_Add * t[p].lazy_Mul % mo + t[p].lazy_Add) % mo;
    t[rson(p)].lazy_Mul = t[rson(p)].lazy_Mul * t[p].lazy_Mul % mo;
    t[rson(p)].lazy_Add = (t[rson(p)].lazy_Add * t[p].lazy_Mul % mo + t[p].lazy_Add) % mo;
    t[p].lazy_Mul = 1;
    t[p].lazy_Add = 0;
}

void update_Add(int p,int l,int r,ll value)
{
    if(l <= t[p].l && r >= t[p].r)//区间被覆盖,就修改;
    {
        t[p].data = (t[p].data + value * (t[p].r - t[p].l + 1) % mo ) % mo;
        t[p].lazy_Add = (t[p].lazy_Add + value) % mo;
        return;
    }
    push_down(p);//如果没有被覆盖,那就需要继续向下找;
    //考虑儿子所维护的区间可能因为懒标记的存在而没有修改,因此将懒标记下放;
    int mid = t[p].l + t[p].r >> 1;
    if(l <= mid)update_Add(lson(p),l,r,value);//覆盖了左儿子就修改左儿子;
    if(r > mid)update_Add(rson(p),l,r,value);//覆盖了右儿子就修改右儿子;
    t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}

void update_Mul(int p,int l,int r,ll value)
{
    if(l <= t[p].l && r >= t[p].r)
    {
        t[p].data = t[p].data * value % mo;
        t[p].lazy_Mul = t[p].lazy_Mul * value % mo;
        t[p].lazy_Add = t[p].lazy_Add * value % mo;
        return;
    }
    push_down(p);
    int mid = t[p].l + t[p].r >> 1;
    if(l <= mid)update_Mul(lson(p),l,r,value);
    if(r > mid)update_Mul(rson(p),l,r,value);
    t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}

ll querySum(int p,int l,int r)
{
    if(l <= t[p].l && r >= t[p].r)return t[p].data % mo;//覆盖了该区间
    push_down(p);
    ll sum = 0;
    int mid = t[p].l + t[p].r >> 1;
    if(l <= mid)sum = (sum + querySum(lson(p),l,r)) % mo;
    if(r > mid)sum = (sum + querySum(rson(p),l,r)) % mo;
    return sum % mo;//累加答案返回左右儿子的和;
}

void updatePa(int u,int v,int x){		/// 节点 u 到节点 v 之前都置零
    while(Top[u] != Top[v]){
        if(Dep[Top[u]] < Dep[Top[v]])swap(u,v);
        update_Mul(1,dfn[Top[u]],dfn[u],x);
        u = Fa[Top[u]];
    }
    if(Dep[u] > Dep[v])swap(u,v);
    update_Mul(1 ,dfn[u] ,dfn[v] ,x);
}
void updateTr(int u){				/// 节点 u的子树都置 1
    update_Mul(1 ,dfn[u] ,dfn[u] + Siz[u] - 1,0);
    update_Add(1 ,dfn[u] ,dfn[u] + Siz[u] - 1,1);
}

void Dfs_Init(int u,int fa,int dep){	/// 第一次 dfs 求出节点深度 重儿子 子树大小
    Dep[u] = dep;
    Siz[u] = 1;
    Fa[u] = fa;
    for(auto it : G[u]){
        if(it == fa)continue;
        Dfs_Init(it ,u ,dep + 1);
        if((!Son[u]) || Siz[Son[u]] < Siz[it])Son[u] = it;
        Siz[u] += Siz[it];
    }
}
void Split(int u,int top){		/// 第二次 dfs 求出 dfn
    Top[u] = top;
    dfn[u] = ++tim;
    nfd[tim] = u;
    if(!Son[u])return ;
    Split(Son[u] ,top);
    for(auto it : G[u]){
        if(it != Fa[u] && it != Son[u])
            Split(it ,it);
    }
}
void Get(int n,int root){		/// 初始化,建图建树,两次dfs接口
    for(int i=0;i<=n+5;++i)dfn[i] = nfd[i] = Son[i] = Siz[i] = a[i] = 0,G[i].clear();
    for(int i=1;i<n;++i){
        int ta,tb;
        ta = read();
        tb = read();
        G[ta].push_back(tb);
        G[tb].push_back(ta);
    }
    build(1 ,1 ,n);
    tim = 0;
    Dfs_Init(root ,-1 ,1);
    Split(root ,root);
}
int main()
{
    n = read();
    Get(n,1);
    q = read();
    for(int i=1;i<=q;++i){
        int op,ta;
        op = read();
        ta = read();
        if(op == 1)updateTr(ta);				/// 充水
        else if(op == 2)updatePa(ta ,1 ,0);			/// 放水
        else{
            printf("%d\n",querySum(1,dfn[ta],dfn[ta]));		/// 单点查询
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值