「ZJOI2019」线段树

传送门

Description

线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 tag 数组为懒标记:

其中函数\(Lson(Node)\)表示\(Node\)的左儿子,\(Rson(Node)\)表示\(Node\)的右儿子。

有一棵 \([1,n]\)上的线段树,编号为\(1\) 。初始时什么标记都没有。

每次修改会把当前所有的线段树复制一份,然后对于这些线段树实行一次区间修改操作。

每次修改后线段树棵数翻倍,第 \(i\)次修改后,线段树共有 \(2^i\) 棵。

每次询问这些线段树中有标记的点的总数。

询问个数\(q\),\(1\leq n,q \leq 10^5\)

Solution

一道很有特色的题目

考察的是对线段树区间修改的认知

\(f_x\)表示\(x\)号节点有标记的线段树占的比例

\(g_x\)表示\(x\)号节点到根路径上有标记的线段树占的比例

首先,把每次区间修改的点分成\(5\)类

  1. 在找到目标节点前经过的节点,它们的标记全部下传,所以\(f_x=\frac{1}{2}f_x,g_x=\frac{1}{2}g_x\)
  2. 目标节点,完全被覆盖,递归过程中被全部被打上标记,\(f_x=\frac{1}{2}+\frac{1}{2}f_x,g_x=\frac{1}{2}+\frac{1}{2}g_x\)
  3. \(2\)类节点的子树内的其它节点,递归过程中不变,\(f_x=f_x,g_x=\frac{1}{2}+\frac{1}{2}g_x\)
  4. 在\(pushdown\)中被访问到的节点,但完全不被覆盖,\(f_x=\frac{1}{2}f_x+\frac{1}{2}g_x,g_x=g_x\)
  5. \(4\)类节点的子树内的其它节点,递归过程不变,\(f_x=f_x,g_x=g_x\)

同时,我们去要维护子树内的\(f_x\)的和

对于\(g_x\)的修改,我们采用打懒标记的方式,当找到\(2\)类节点时,直接对它打上\(*\frac{1}{2}\)的标记

Code?

#include<bits/stdc++.h>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define ll long long
#define reg register
inline int read()
{
    reg int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}
const int MN=1e5+5,mod=998244353,Inv2=499122177;
int N,M;
int f[MN<<3],g[MN<<3],sf[MN<<3],lz[MN<<3];
#define Add(x,y) (((x)+(y))%mod)
#define Mul(x,y) (1ll*(x)*(y)%mod)
#define ls (x<<1)
#define rs (x<<1|1)
void C(int x,int val){lz[x]=Mul(lz[x],val);g[x]=Add(Mul(g[x],val),1-val+mod);}
void down(int x){if(lz[x]^1)C(ls,lz[x]),C(rs,lz[x]),lz[x]=1;}
void up(int x){sf[x]=Add(f[x],Add(sf[ls],sf[rs]));}
void Build(int x,int l,int r)
{
    lz[x]=1;if(l==r) return;
    reg int mid=(l+r)>>1;
    Build(ls,l,mid);Build(rs,mid+1,r);
}
void Modi(int x,int l,int r,int a,int b)
{
    if(r<a||l>b)
    {
        f[x]=Mul(Inv2,Add(f[x],g[x]));
        up(x);return;
    }
    if(l>=a&&r<=b)
    {
        f[x]=Add(Inv2,Mul(Inv2,f[x]));
        C(x,Inv2);up(x);return;
    }
    down(x);f[x]=Mul(Inv2,f[x]);g[x]=Mul(Inv2,g[x]);
    reg int mid=(l+r)>>1;
    Modi(ls,l,mid,a,b);
    Modi(rs,mid+1,r,a,b);
    up(x);
}
int main()
{
    N=read();M=read();Build(1,1,N);
    reg int opt,l,r,Num=1;
    while(M--)
    {
        opt=read();
        if(opt==2) printf("%d\n",Mul(sf[1],Num));
        else l=read(),r=read(),Modi(1,1,N,l,r),Num=Mul(2ll,Num);
    }
    return 0;
}


Blog来自PaperCloud,未经允许,请勿转载,TKS!

原文地址:https://www.cnblogs.com/PaperCloud/p/10657585.html

时间: 2024-08-30 10:27:19

「ZJOI2019」线段树的相关文章

「模板」 线段树——区间乘 &amp;&amp; 区间加 &amp;&amp; 区间求和

「模板」 线段树--区间乘 && 区间加 && 区间求和 <题目链接> 原来的代码太恶心了,重贴一遍. #include <cstdio> int n,m; long long p; class SegmentTree { private: struct Node { int l,r; long long v,mul,add; Node *c[2]; Node(int l,int r):l(l),r(r),mul(1LL),add(0LL) { c[

「模板」线段树静态开点(单点+区间修改)、动态开点

相关讲解资料: 树状数组:https://blog.csdn.net/qq_34374664/article/details/52787481 (线段树预备) 线段树讲解: 初学版:https://blog.csdn.net/zearot/article/details/52280189 进阶完整版:https://www.cnblogs.com/AC-King/p/7789013.html 代码: 完整注释模板一张,参(chao)考(xi)楼上的博客 #include<iostream> #

&#183;专题」 线段树

PKU暑期培训第一天,这次培训人很多,但是感觉打酱油的也不少,不知道大牛有多少. 第一天都是讲线段树的,课件的话和往常一样,没什么变化. 具体的话,讲了线段树和树状数组.线段树的话讲了单点更新,成段更新,扫描线已经离散化. 然后随便提了提树状数组.估计明天再讲一点点,然后接着是讲并查集,多串匹配什么的. 线段树的题目我做得很少,以前看过HH大神的模板,想模仿来着,其实也没什么理解. 重新理解一下线段树吧. 线段树的用途,主要是维护区间问题,比如区间的单点更新操作,成段更新,扫描线等等.当然还有一

·专题」 线段树

初学线段树(SegmentTree) 从HH大神那你学来的模板风格. 感觉确实相当飘逸. 现在做了4题..单点更新的, 想放上来,,以后慢慢整理!! ·单点更新」 ·刷题参考」 hdu1166 敌兵布阵 线段树第一题,单点更新第一题,可以作为线段树的模板, 思路:兵工厂作为数量n映射作为线段总长,更具输入进行单点的更新与查询,sub操作可以理解为add一个负数 Query操作:区间求和 update操作:单点的增减 1 #include<cstdio> 2 3 #define lson l,

loj#2269. 「SDOI2017」切树游戏

还是loj的机子快啊... 普通的DP不难想到,设F[i][zt]为带上根玩出zt的方案数,G[i][zt]为子树中的方案数,后面是可以用FWT优化的 主要是复习了下动态DP #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; co

「ZJOI2019」Minimax 搜索(动态dp)

Address loj3044 Solution 考虑对 \(k\in [l-1,r]\) 分别求出有多少个集合 \(S\) 满足 \(w(S)\le k\),记作 \(ans_k\). 先求出 \(1\) 的初始权值 \(W\). 记 \(val(x)\) 表示 \(x\) 的权值.枚举 \(k\),现在对于每个叶子 \(u\),如果 \(u\in S\),那么 \(val(u)\in [u-W,u+W]\),否则 \(val(u)=W\). 我们发现,把叶子节点的权值改成 \(W\) 肯定是

【tyvj】P2065 「Poetize10」封印一击(贪心+线段树/差分)

http://new.tyvj.cn/p/2065 我就不说我很sb的用线段树来维护值...... 本机自测的时候想了老半天没想出怎么维护点在所有区间被多少区间包含的方法.最后一小时才想出来线段树(果然太弱) .. 首先想到贪心,答案一定是某个区间的右端点...(这个很容易想也容易证,我就不说了.....) 然后按右端点排序 然后我维护了个左端点前缀和,将来枚举每一个右端点的时候所得到的答案就是sum[n]-sum[i]-he+ge*a[i].r he表示包含右端点的所有区间的左端点之和,ge表

「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)

学习了新姿势..(一直看不懂大爷的代码卡了好久T T 首先数字范围那么小可以考虑枚举众数来计算答案,设当前枚举到$x$,$s_i$为前$i$个数中$x$的出现次数,则满足$2*s_r-r > 2*s_l-l$的区间$[l+1,r]$其众数为$x$,这个显然可以用一个数据结构来维护. 直接扫一遍效率是$O($数字种类数$*nlogn)$的,无法承受,但是我们发现,对于每一段非$x$的数,$2*s_i-i$是公差为$-1$的等差数列,所以它们对答案的贡献实际上可以一次性计算.设$L$为一段非$x$数

「BZOJ3600」没有人的算术 替罪羊树+线段树

题目描述 过长--不想发图也不想发文字,所以就发链接吧-- 没有人的算术 题解 \(orz\)神题一枚 我们考虑如果插入的数不是数对,而是普通的数,这就是一道傻题了--直接线段树一顿乱上就可以了. 于是我们现在只需要解决一个问题--维护这些数的大小关系. 由于这些数具有有序性,我们可以将这些数的值重记为一个数,这样就可以无脑地比较了.并且,由于这些值的大小可能会随着插入而更改,所以要用一棵平衡树来维护. 那么问题来了,这个数取什么值比较好呢? 首先当然可以是排名,不过如果使用排名,每次访问值的时