題解 P5280 【[ZJOI2019]線段樹】
看了下題解,感覺有點弄懂了,但是好像還是不是很清楚,打算一邊寫草稿,一邊寫題,一邊寫題解,用以加深印象。
題目大意
就是你有一顆表示區間 \([1,n]\) 的線段樹。一開始樹上的 \(tag\) 均為 \(0\) 。你接下來會進行 \(m\) 次操作。
1 l r
:假設當前手上有 \(t\) 棵線段樹,你需要會把每棵線段樹複製兩份( \(tag\) 陣列也一起復制),原先編號為 \(i\) 的線段樹複製得到的兩棵編號為 \(2i-1\) 與 \(2i\) ,在複製結束後,一共有 \(2t\) 棵線段樹。接著,你需要會對所有編號為奇數的線段樹進行一次 \(\operatorname{Modify}(root,1,n,l,r)\)
其中 \(\operatorname{Modify}\) 操作的函式如下:
2
:定義一棵線段樹的權值為它上面有多少個節點 \(tag\) 為 \(1\)。輸出所有線段樹的權值和。
草稿 和 題解
有一個非常 \(naive\) 的做法,就是每一次暴力複製線段樹,然後進行相應的操作,但是我們發現這個方法最多隻能跑 \(m=10\) ,非常不可行。
然後我們考慮這個複製線段樹的操作意義在於何?他無非是求對於到當前操作選或者不選的所有方案中, \(tag\) 的數量和,這個對於我們來說就是變著法的計數,我們考慮 \(\text{dp}\) 。
首先我們考慮狀態的設計,我們發現,對於當前已經有的線段樹中,同一個節點的操作是可以一起看的,因為我們只需要知道最後的和而並不需要其中具體的值,所以我們不妨設 \(f_{i,j}\)
我打算 \(he\) 一下題解的圖(不代表著我在一邊看題解一邊寫題啊……),就用神 \(\text{Sooke}\) 的吧:
可以發現,對於一次 \(\operatorname{Modify}\) ,整個線段樹的點只會有 \(5\) 種情況,我們可以分開討論:
-
對於白色點,即是會被執行 \(\operatorname{Pushdown}\) 的點,是不會將這個 \(tag\) 標記在這裡最後留下,因為他會將其傳遞到自己的下方 。
-
對於黑色點,即是你最終打 \(tag\) 的點。
-
對於橙色點,是需要接受 \(tag\) 的點,但是不需要打 \(tag\) 。
-
對於黃色點和灰色點,都是在這一次操作過程中不需要變動的點,我們只需要將其的 \(f\) 陣列乘以 \(2\) 即可。
對於上述的操作,我們發現,我們還需要找出這個點上方有多少 \(tag\) 要繼承。這個東西也需要維護,因為我們發現我們不能在修改的時候直接得出。我們不妨設 \(g_{i,j}\) 表示節點 \(i\) 到第 \(j\) 個操作時,有多少棵線段樹在 \(i\) 點及 \(i\) 點上方的節點中有 \(tag\) 。同樣的,對於這個東西的維護,對於不同的節點也是不一樣的。
-
對於白色點,我們發現這個東西在進行操作的那一半線段樹中是會被清零的,因為他們節點的 \(tag\) 會全部下放。
-
對於黑色點和灰色點,由於其自己或上方的點必定會打上 \(tag\) ,所以 \(g\) 的增量就是線段樹個數的增量,即 \(2^j\) 。
-
對於黃色點和橙色點,由於並沒有加 \(tag\) 或進行下傳 ,同時其上方的點的 \(tag\) 也不會有變,所以說對於他來說,自己的 \(g\) 並沒有變化,乘以 \(2\) 即可。
然後發現我們就可以維護了。
如果直接判斷點的顏色再進行相應的操作是 \(O(n^2)\) 的,時間還是不滿足,所以我們考慮線上段樹上維護 \(\text{dp}\) 。然後應該就可以做了。具體的操作方式可以看程式碼,雖然還沒寫完。
#include<bits/stdc++.h>
using namespace std;
#define Lint long long
const int N=1e5+5,M=1e5+5;
const Lint MOD=998244353;
int n,m,cnt=0;
Lint ksm[M];
struct Seg_Tree
{
struct Node{Lint sum,f,g,tagf,tagg1,tagg2;}tr[N<<2];
void build(int u,int l,int r)
{
tr[u].f=tr[u].g=tr[u].sum=0;
tr[u].tagf=tr[u].tagg1=1,tr[u].tagg2=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
void up(int u){tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum+tr[u].f)%MOD;}
void updataf(int u,Lint z)
{
tr[u].f=(tr[u].f*z)%MOD;
tr[u].sum=(tr[u].sum*z)%MOD;
tr[u].tagf=(tr[u].tagf*z)%MOD;
}
void updatag(int u,Lint z1,Lint z2)
{
tr[u].g=(tr[u].g*z1%MOD+z2)%MOD;
tr[u].tagg1=tr[u].tagg1*z1%MOD;
tr[u].tagg2=(tr[u].tagg2*z1%MOD+z2)%MOD;
}
void down(int u)
{
updataf(u<<1,tr[u].tagf);
updataf(u<<1|1,tr[u].tagf);
tr[u].tagf=1;
updatag(u<<1,tr[u].tagg1,tr[u].tagg2);
updatag(u<<1|1,tr[u].tagg1,tr[u].tagg2);
tr[u].tagg1=1,tr[u].tagg2=0;
}
void opt(int u,int l,int r,int x,int y,int now)
{
if(x<=l&&r<=y)
{
tr[u].sum=(tr[u].sum+MOD-tr[u].f)%MOD;
tr[u].f=(tr[u].f+ksm[now-1])%MOD;
tr[u].sum=(tr[u].sum+tr[u].f)%MOD;
updatag(u,1,ksm[now-1]);//black
if(l!=r)
{
down(u);
updataf(u<<1,2);
updataf(u<<1|1,2);//grey
up(u);
}
return ;
}
int mid=(l+r)>>1;
down(u);//white
if(x<=mid) opt(u<<1,l,mid,x,y,now);
else
{
tr[u<<1].sum=(tr[u<<1].sum+MOD-tr[u<<1].f)%MOD;
tr[u<<1].f=(tr[u<<1].f+tr[u<<1].g)%MOD;
tr[u<<1].sum=(tr[u<<1].sum+tr[u<<1].f)%MOD;//orange
if(l!=mid)
{
down(u<<1);
updataf(u<<2,2);
updataf(u<<2|1,2);//yellow
up(u<<1);
}
updatag(u<<1,2,0);//orange and yellow
}
if(y>mid) opt(u<<1|1,mid+1,r,x,y,now);
else
{
tr[u<<1|1].sum=(tr[u<<1|1].sum+MOD-tr[u<<1|1].f)%MOD;
tr[u<<1|1].f=(tr[u<<1|1].f+tr[u<<1|1].g)%MOD;
tr[u<<1|1].sum=(tr[u<<1|1].sum+tr[u<<1|1].f)%MOD;//orange
if(mid+1!=r)
{
down(u<<1|1);
updataf((u<<1|1)<<1,2);
updataf((u<<1|1)<<1|1,2);//yellow
up(u<<1|1);
}
updatag(u<<1|1,2,0);//orange and yellow
}
up(u);
}
}t;
int main()
{
cin>>n>>m,ksm[0]=1;
for(int i=1;i<=m;++i) ksm[i]=(ksm[i-1]<<1)%MOD;
t.build(1,1,n);
while(m--)
{
int opt,x,y;
scanf("%d",&opt);
if(opt==1)
{
scanf("%d%d",&x,&y);
t.opt(1,1,n,x,y,++cnt);
}
else printf("%lld\n",t.tr[1].sum);
}
return 0;
}