1. 程式人生 > 實用技巧 >題解 P5251 【[LnOI2019]第二代圖靈機】

題解 P5251 【[LnOI2019]第二代圖靈機】

轉載註明來源:https://www.cnblogs.com/syc233/p/13673494.html


珂朵莉樹+尺取法+線段樹。


大體思路是珂朵莉樹維護顏色段,線段樹維護區間和、區間最值,3、4操作在珂朵莉樹上做尺取法。

主要說一下尺取法的細節:

操作3

詢問區間 \([l,r]\) 中包含所有(一共 \(c\) 種)顏色,數字和最小的子區間的數字和。

先將詢問區間在珂朵莉樹上取出來,固定區間左端點,移動右端點,用桶維護區間內每種顏色的出現次數和區間顏色總數。

當區間包含所有顏色時停止移動右端點,即算出包含所有顏色的最短區間。對於左右端點的位置進行分類討論:

  • 若左右端點同屬一個塊,即 \(c=1\)
    ,因為要求最小數字和,所以線段樹取塊內最小值即可。
  • 否則,令左端點在塊 \([l1,r1]\) 中,右端點在 \([l2,r2]\) 中,則取區間 \([r1,l2]\)
inline int query1(int l,int r)
{
	IT itr=split(r+1),itl=split(l);
	memset(sta,0,sizeof(sta));cnt=0;
	int ans=INF;
	for(IT l=itl,r=itl;l!=itr;++l)
	{
		while(r!=itr&&cnt!=c)
			Add(r->val,r->r-r->l+1),++r;
		--r;
		if(cnt==c)
		{
			if(l==r) ans=min(ans,st.query_min(1,l->l,l->r));
			else ans=min(ans,st.query_sum(1,l->r,r->l));
		}
		++r;
		Del(l->val,l->r-l->l+1);
	}
	return ans==INF?-1:ans;
}

操作4

表示詢問區間 \([l,r]\) 中沒有重複顏色,數字和最大的子區間的數字和。

首先只取一個數顯然是可行的,那麼 \(ans\) 的初值即為區間最大值。

然後類似操作3,將區間從珂朵莉樹上取出來,做尺取法。

因為只取一個數的情況已經處理,所以尺取時左右端點不能在同一個塊中。要保證區間中沒有重複顏色,那麼每次擴充套件右端點時只能加入大小等於 \(1\) 的塊 。然而合法區間的右端點是可能在一個大小大於 \(1\) 的塊中的,因為可以只取這個塊的第一個數,在每次擴充套件完後臨時加入這種塊即可。

inline int query2(int l,int r)
{
	IT itr=split(r+1),itl=split(l);
	memset(sta,0,sizeof(sta));cnt=0;
	int ans=st.query_max(1,l,r);
	for(IT l=itl,r=itl;l!=itr;++l)
	{
		if(l==r)
			Add(r->val,1),++r;
		while(r!=itr&&!sta[r->val]&&r->r-r->l+1==1)
			Add(r->val,1),++r;
		bool flag=false;
		if(r!=itr&&!sta[r->val])
			Add(r->val,1),++r,flag=true;
		--r;
		if(l!=r) ans=max(ans,st.query_sum(1,l->r,r->l));
		Del(l->val,1);
		if(flag)
			Del(r->val,1),--r;// 將臨時加入的塊刪除
		++r;
	}
	return ans;
}

完整程式碼就沒必要放上來了吧,相信來做這道題的人都會珂朵莉樹和線段樹。