1. 程式人生 > 實用技巧 >題解 P5280 【[ZJOI2019]線段樹】

題解 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}\)

表示節點 \(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;
}