1. 程式人生 > 實用技巧 >「Luogu P6749 『MdOI R3』Yoshino」

「Luogu P6749 『MdOI R3』Yoshino」

先膜拜一發 BF.

題目大意

給出一個序列,每次可以將一段區間賦值為一段公差為 \(1\) 的等差數列,或者查詢這個序列的逆序對個數.

分析

請先通過 動態逆序對,並且食用 P4062這篇題解.

如果您已經認真做了上面兩件事,那麼多少會對這題有點思路了.

(下面提到的 等差數列 如果沒有特殊說明就是指公差為 \(1\) 的等差數列)

先考慮一次覆蓋會產生的逆序對的個數:

首先可以知道在這樣一個等差數列內部是不會有逆序對的.

那麼產生的逆序對的個數就等於在等差數列前面的所有數大於等差數列中的數的個數的總和.後面同理.

動態計算這個東西自然就會想到用樹套樹去維護.

不過這裡不是一個數,而是一個等差數列,那麼就可以用到

這裡所提到的做法了,對於內層的權值線段樹上不只要維護區間和(\(\sum\limits_{i=l}^{r}sum_i\))還需要維護一個等差數列乘區間內的每個數(\(\sum\limits_{i=l}^{r}[(i-l+1)sum_i]\)),這樣就可以通過一些簡單的計算得到一個新增一個等差數列所得的貢獻了.

接下來考慮如何刪掉一次覆蓋.

可以發現這題其實只有區間覆蓋這一個操作(逆序對個數是時時計算的),所以 ODT 在這題中的複雜度是正確的.一次覆蓋最多隻會增加 \(3\) 個區間,總區間數的上界是 \(n+3m\) 顯然是可以的.所以每次只需要在 ODT 上把覆蓋到的所有區間的貢獻在樹套樹上減掉,再把這次覆蓋的貢獻加上就可以了.

這樣就可以直接用樹狀陣列套權值線段樹以及一個 ODT 來維護了.(其實和 BF 的做法沒啥區別)

不過,樹狀陣列套線段樹見多了也就沒啥意思了,所以來分享一下線段樹套線段樹的做法.

對於外層的線段樹上是區間覆蓋的標記,考慮標記永久化,對於刪掉的覆蓋需要將這些標記也刪掉,那麼如果存在一個節點的上面是有覆蓋標記的,那麼在這個節點的祖先和子孫中是不可能有存在標記的節點,因為標記不能下傳的原因,所以如果需要查詢的節點時某個被覆蓋的節點的子孫,那麼需要直接在這個節點的標記上查詢,也就是計算兩個等差數列相連之後的逆序對個數,這個東西可以通過簡單的畫圖和計算得出,程式碼如下:

int Calc(int a,int lena,int b,int lenb)//第一個等差數列的首項為 a,長度為 lena,第二個等差數列的首項為 b,長度為 lenb
{
	if(a<=b)//如果 a 的首項小於 b,那麼就把 a 中沒有貢獻的部分去掉,是的保證 a 的首項大於 b
	{
		lena-=(b-a+1);
		a=b+1;
	}
	if(lena<=0)//如果減掉之後第一個等差數列不存在了,那麼就刪掉
	{
		return 0;
	}
    //一下內容建議自行畫圖理解
	int f=min(a-b,lenb);
	int l=min(a-b+lena-1,lenb);
	int len=l-f+1;
	return (f+l)*len/2+(lena-len)*lenb;
}

寫出來常數可能有點大,需要一些卡常技巧:

對於內層的線段樹需要用標記永久化,不然每次下傳標記的時候需要重新開節點,會導致 MLE,對於外層的線段樹修改時對於被完全覆蓋的區間就不需要在對應的線段樹上修改了,可以直接使用上面的這個 Calc 函式.

程式碼細節挺多的.

程式碼

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
namespace IO
//快讀模板
using namespace IO;
using namespace std;
const int TOP=3e4+2;
const int MAXN=3e4+5;
int n,m;
int arr[MAXN];
long long answer=0;
namespace InSGT//內層線段樹與 P4062 基本相同
{
	struct LazyTag
	{
		int add;
		inline void Clean()
		{
			add=0;
		}
	}for_make;
	inline LazyTag MakeTag(int add)
	{
		for_make.add=add;
		return for_make;
	}
	struct SegmentTree
	{
		int len,lson,rson,sum;
		long long sum_;
		LazyTag tag;
	}sgt[MAXN*1000];
	int sgt_cnt=0;
	#define LSON sgt[now].lson
	#define RSON sgt[now].rson
	#define MIDDLE ((left+right)>>1)
	#define LEFT LSON,left,MIDDLE
	#define RIGHT RSON,MIDDLE+1,right
	#define NOW now_left,now_right
	inline void PushUp(int now)
	{
        //因為是標記永久化,所以需要在 PushUp 的時候把當前節點的標記的貢獻也加上
		sgt[now].sum=sgt[LSON].sum+sgt[RSON].sum+sgt[now].tag.add*sgt[now].len;
		sgt[now].sum_=sgt[RSON].sum_+1ll*sgt[RSON].sum*(sgt[now].len-sgt[RSON].len)+sgt[LSON].sum_+(sgt[now].len+1)*sgt[now].len/2*sgt[now].tag.add;
	}
	inline void Down(LazyTag tag,int &now,int left,int right)
	{
		sgt[now].tag.add+=tag.add;
		sgt[now].sum+=sgt[now].len*tag.add;
		sgt[now].sum_+=(sgt[now].len+1)*sgt[now].len/2*tag.add;
	}
	void UpdataAdd(int now_left,int now_right,int add,int &now,int left=1,int right=TOP)
	{
		if(now_right<left||right<now_left)
		{
			return;
		}
		if(!now)
		{
			sgt[now=++sgt_cnt].len=right-left+1;
		}
		if(now_left<=left&&right<=now_right)
		{
			Down(MakeTag(add),now,left,right);
			return;
		}
		UpdataAdd(NOW,add,LEFT);
		UpdataAdd(NOW,add,RIGHT);
		PushUp(now);
	}
	int QuerySum(int now_left,int now_right,int now,int left=1,int right=TOP,int tag=0)//在查詢的時候需要記錄從根節點到當前節點一路上的標記的和
	{
		if(now_right<left||right<now_left)
		{
			return 0;
		}
		if(now_left<=left&&right<=now_right)
		{
			return sgt[now].sum+tag*(right-left+1);
		}
		return QuerySum(NOW,LEFT,tag+sgt[now].tag.add)+QuerySum(NOW,RIGHT,tag+sgt[now].tag.add);
	}
	int QuerySum_(int now_left,int now_right,int now,int left=1,int right=TOP,int tag=0)
	{
		if(now_right<left||right<now_left)
		{
			return 0;
		}
		if(now_left<=left&&right<=now_right)
		{
			int len=right-left+1;
			return sgt[now].sum_+(sgt[now].sum+tag*len)*(left-now_left)+(len+1)*len/2*tag;
		}
		return QuerySum_(NOW,LEFT,tag+sgt[now].tag.add)+QuerySum_(NOW,RIGHT,tag+sgt[now].tag.add);
	}
	#undef LSON
	#undef RSON
	#undef MIDDLE
	#undef LEFT
	#undef RIGHT
	#undef NOW
}
namespace OutSGT//外層線段樹和 ODT
{
	int Calc(int a,int lena,int b,int lenb)
	{
		if(a<=b)
		{
			lena-=(b-a+1);
			a=b+1;
		}
		if(lena<=0)
		{
			return 0;
		}
		int f=min(a-b,lenb);
		int l=min(a-b+lena-1,lenb);
		int len=l-f+1;
		return (f+l)*len/2+(lena-len)*lenb;
	}
	struct SegmentTree
	{
		int root,tag;
	}sgt[MAXN*4];
	#define LSON (now<<1)
	#define RSON (now<<1|1)
	#define MIDDLE ((left+right)>>1)
	#define LEFT LSON,left,MIDDLE
	#define RIGHT RSON,MIDDLE+1,right
	#define NOW now_left,now_right
	void Build(int now=1,int left=1,int right=n)
	{
		if(left==right)//對於葉節點相當於是覆蓋,所以只需要打上標記
		{
			sgt[now].tag=arr[left];
			return;
		}
		REP(i,left,right)
		{
			InSGT::UpdataAdd(arr[i],arr[i],1,sgt[now].root);
		}
		Build(LEFT);
		Build(RIGHT);
	}
	void Cover(int now_left,int now_right,int val,int add,int now=1,int left=1,int right=n)
	{
		if(now_right<left||right<now_left)
		{
			return;
		}
		if(now_left<=left&&right<=now_right)//對於被完全覆蓋的區間只需要打上標記
		{
			sgt[now].tag+=add*(val+left-now_left);
			return;
		}
		InSGT::UpdataAdd(val+max(now_left,left)-now_left,val+min(now_right,right)-now_left,add,sgt[now].root);//在記憶體線段樹上修改
		Cover(NOW,val,add,LEFT);
		Cover(NOW,val,add,RIGHT);
	}
	int QueryL(int now_left,int now_right,int val,int now=1,int left=1,int right=n)//查詢前面大於的個數
	{
		if(now_left<=left)
		{
			return 0;
		}
		if(sgt[now].tag)
		{
			return Calc(sgt[now].tag,min(right,now_left-1)-left+1,val,now_right-now_left+1);
		}
		if(right<now_left)
		{
			return InSGT::QuerySum_(val+1,val+now_right-now_left,sgt[now].root)+InSGT::QuerySum(val+now_right-now_left+1,TOP,sgt[now].root)*(now_right-now_left+1);
		}
		return QueryL(NOW,val,LEFT)+QueryL(NOW,val,RIGHT);
	}
	int QueryR(int now_left,int now_right,int val,int now=1,int left=1,int right=n)//查詢後面小於自己的個數
	{
		if(right<=now_right)
		{
			return 0;
		}
		if(sgt[now].tag)
		{
			return Calc(val,now_right-now_left+1,sgt[now].tag+max(left,now_right+1)-left,right-max(left,now_right+1)+1);
		}
		if(now_right<left)
		{
			return InSGT::QuerySum(1,val+now_right-now_left,sgt[now].root)*(now_right-now_left+1)-InSGT::QuerySum_(val,val+now_right-now_left,sgt[now].root);
		}
		return QueryR(NOW,val,LEFT)+QueryR(NOW,val,RIGHT);
	}
	#undef LSON
	#undef RSON
	#undef MIDDLE
	#undef LEFT
	#undef RIGHT
	#undef NOW
	struct Node//ODT 部分
	{
		int l,r;
		mutable int val;
		Node(int left=0,int right=-1,int v=0)
		{
			l=left;
			r=right;
			val=v;
		}
		bool operator <(const Node &b)const
		{
			return l<b.l;
		}
	};
	set<Node>s;
	#define IT set<Node>::iterator
	IT Split(int pos)//分裂區間,ODT 基本操作
	{
		IT place=s.lower_bound(Node(pos));
		if(place!=s.end()&&place->l==pos)
		{
			return place;
		}
		--place;
		int left=place->l,right=place->r;
		int val=place->val;
		Cover(left,right,val,-1);
		s.erase(place);
		Cover(left,pos-1,val,1);
		Cover(pos,right,val+pos-left,1);
		s.insert(Node(left,pos-1,val));
		return s.insert(Node(pos,right,val+pos-left)).first;
	}
	void Assign(int left,int right,int val)//區間覆蓋部分
	{
		IT place_left=Split(left),place_right=Split(right+1);
		while(1)
		{
			IT i=s.lower_bound(Node(left));
			if(i->l==right+1)
			{
				break;
			}
			answer-=QueryL(i->l,i->r,i->val);
			answer-=QueryR(i->l,i->r,i->val);
			Cover(i->l,i->r,i->val,-1);
			s.erase(i);
		}
		Cover(left,right,val,1);
		answer+=QueryL(left,right,val);
		answer+=QueryR(left,right,val);
		s.insert(Node(left,right,val));
	}
	#undef IT
}
namespace ReversePair//最開始時直接樹狀陣列計算逆序對
{
	int tree[MAXN];
	int LowBit(int now)
	{
		return now&-now;
	}
	void Add(int num)
	{
		for(int now=num;now<=TOP;now+=LowBit(now))
		{
			tree[now]++;
		}
	}
	int Query(int num)
	{
		int result=0;
		for(int now=num;now;now-=LowBit(now))
		{
			result+=tree[now];
		}
		return result;
	}
	void Calc()
	{
		REP(i,1,n)
		{
			Add(arr[i]);
			answer+=i-Query(arr[i]);
		}
	}
}
int main()
{
	Read(n,m);
	REP(i,1,n)
	{
		Read(arr[i]);
		OutSGT::s.insert(OutSGT::Node(i,i,arr[i]));
	}
	OutSGT::s.insert(OutSGT::Node(n+1,n+1,0));
	OutSGT::Build();
	ReversePair::Calc();
	int opt,l,r,val;
	REP(i,1,m)
	{
		Read(opt);
		if(opt==1)
		{
			Read(l,r,val);
			OutSGT::Assign(l,r,val);
		}
		if(opt==2)
		{
			Writeln(answer);
		}
	}
	return 0;
}