Luogu P5280 [ZJOI2019]线段树
用时:看题解一上午,写代码120min
省选打完了,继续停课...
gg说要做历年省选题,还要看博客...公开处刑...
每个\(modify\)操作会复制1倍线段树并修改。
然而实际上不需要维护那么多线段树,维护1棵并在树上dp即可。
对于不同状态的节点有不同的维护操作,需要分类讨论。
(Linux还没整画图软件,就借用下洛谷题解的图吧...)
- (白)包含modify区间
- (黑)能被遍历,属于modify区间
- (橙)能被遍历,不属于modify区间
- (灰)不能被遍历,属于modify区间
- (黄)不能被遍历,不属于modify区间
设:
\(total\)表示本次modify增加的线段树个数。
\(f[i]\)表示节点\(i\)在所有线段树中,有tag的个数。
\(g[i]\)表示节点\(i\)在所有线段树中,从\(1\)到\(i\)的路径上都没有tag的个数。
1.
由于要向下找区间,所以\(i\)的tag会被pushdown下去。
因此,从\(1\)到\(i\)都一定没有tag。
2.
modify即在\(i\)上加一个tag,所以一定有tag。
3.
\(i\)没有modify,但会被祖先pushdown。
祖先若有tag,pushdown后i也有tag,有tag的个数即为\(tot-g[i]\);
祖先若没有tag,则直接转移。
这里要注意顺序,先修改f再修改g。
4.
\(i\)的祖先被打了tag,但不需要pushdown。
所以\(i\)是否有tag直接转移,但祖先一定有tag。
5.
\(i\)根本不会被遍历到。直接转移。
情况\(1,2,3\)在维护线段树时暴力转移即可;
情况\(4,5\)可以用\(lazy\)标记维护。
在转移\(2\)时给\(4\)打标记,在转移\(3\)时给\(5\)打标记。
初始化:\(f[i]=0, g[i]=1\)。
由于是乘法,\(lazy_f[i] = lazy_g[i] = 1\)
\(sum[i]\)表示\(i\)的子树内有tag的个数。
\(sum[i] = f[i] + sum[ls] + sum[rs]\)
\(sum[1]\)即为答案。
注意:
- 变量全部long long。
- 防止RE,数组开\(8\)倍。
- 几乎每个修改前都要先\(pushdown\),改完要\(pushup\)。
(我就是在从\(3\)修改\(5\)时,没有先把\(3\) \(pushdown\),所以WA了)
代码如下
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define Mogeko qwq
using namespace std;
#define Mid (l+r>>1)
#define ls (now<<1)
#define rs (now<<1|1)
const int maxn = 8e5+10;
const int mod = 998244353;
int n,m,op,x,y;
long long k,f[maxn],g[maxn],lazf[maxn],lazg[maxn],sum[maxn];
int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while('0' <= ch && ch <= '9'){
x = x*10 + ch-'0';
ch = getchar();
}
return x;
}
void build(int l,int r,int now){
g[now] = lazf[now] = lazg[now] = 1;
if(l == r) return;
int mid = Mid;
build(l,mid,ls);
build(mid+1,r,rs);
}
void pushup(int now){
sum[now] = (f[now] + (sum[ls] + sum[rs]) %mod) %mod;
}
void push_f(int now,long long x){
(f[now] *= x) %= mod;
(lazf[now] *= x) %= mod;
(sum[now] *= x) %= mod;
}
void push_g(int now,long long x){
(g[now] *= x) %= mod;
(lazg[now] *= x) %= mod;
}
void pushdown(int now){
if(lazf[now] > 1)
push_f(ls,lazf[now]), push_f(rs,lazf[now]);
if(lazg[now] > 1)
push_g(ls,lazg[now]), push_g(rs,lazg[now]);
lazf[now] = lazg[now] = 1;
}
void upd(int now,int id){
if(id == 1)
(g[now] += k) %= mod;
else if(id == 2){
(f[now] += k) %= mod;
push_f(ls,2), push_f(rs,2);
}
else if(id == 3){
pushdown(now);
(f[now] += ((k - g[now]) %mod+mod)%mod) %= mod;
(g[now] += g[now]) %= mod;
push_f(ls,2), push_f(rs,2);
push_g(ls,2), push_g(rs,2);
}
pushup(now);
}
void modify(int L,int R,int l,int r,int now){
pushdown(now);
if(L == l && R == r){
upd(now,2);
return;
}
int mid = Mid;
upd(now,1);
if(R <= mid){
modify(L,R,l,mid,ls);
upd(rs,3);
}
else if(L >= mid+1){
modify(L,R,mid+1,r,rs);
upd(ls,3);
}
else {
modify(L,mid,l,mid,ls);
modify(mid+1,R,mid+1,r,rs);
}
pushup(now);
}
int main(){
n = read(),m = read();
build(1,n,1);
k = 1;
while(m--){
op = read();
if(op == 1){
x = read(),y = read();
modify(x,y,1,n,1);
(k += k) %= mod;
}
if(op == 2)
printf("%lld\n",sum[1]);
}
return 0;
}