題解 P4344 【[SHOI2015]腦洞治療儀】


前言

這道題目呢,看上去很難,實際上我們可以用線段樹解決這道題目。

正文

我們維護 sumlentaglmaxrmaxans

sum 就是這段區間非腦洞的個數

len 就是這段區間的長度

tag 就是我們的 lazy_tag

lmax 就是從左開始的連續腦洞個數

rmax 就是從右開始的連續腦洞個數

ans 就是這段區間最大的連續腦洞

建樹

由於 len 是不變的,所以我們可以建樹的時候就求出 len

t[num].len=r-l+1;

pushup

sum

sum 就是左子樹和右子樹的 sum 的和。

t[num].sum=t[ls].sum+t[rs].sum;

lmax

lmax 的話有兩種情況

\(1\) 種情況

aaasajdfhiujhkja.png

lmax=左子樹的 lmax

\(2\) 中情況

asdssssajdfhiujhkja.png

lmax=左子樹的 len + 右子樹的 lmax

if(t[ls].lmax==t[ls].len)t[num].lmax=t[ls].len+t[rs].lmax;
else t[num].lmax=t[ls].lmax;

rmax

rmax 的話也兩種情況

\(1\) 種情況

kja.png

rmax=右子樹的 rmax

df.png

lmax=右子樹的 len + 左子樹的 rmax

if(t[rs].rmax==t[rs].len)t[num].rmax=t[rs].len+t[ls].rmax;
else t[num].rmax=t[rs].rmax;

ans

ans 的話有 \(3\) 種情況

\(1\) 種情況

asdsajdfhiujhkja.png

ans=左子樹的 ans

\(2\) 種情況

asdasajdfhiujhkja.png

ans=右子樹的 ans

\(3\) 種情況

aasdasajdfhiujhkja.png

ans=左子樹的 rmax+右子樹的 lmax

t[num].ans=max(max(t[ls].ans,t[rs].ans),t[ls].rmax+t[rs].lmax);

pushdown

tag

我們的 tag3 種值,分別為 012

0 表示什么都沒有

1 表示全部為腦洞

2 表示全部不為腦洞

0

0 的話,代表沒有任何操作,不要管。

1

我們對照上面的發現:

anslmaxrmax 都為 len

sum 則為 0

tag 的標記當然要打啦。

void down1(int num){
	t[num].ans=t[num].lmax=t[num].rmax=t[num].len;
	t[num].sum=0;
	t[num].tag=1;
}
2

我們對照上面的發現:

anslmaxrmax 都為 0

sum 則為 len

tag 的標記當然要打啦。

void down2(int num){
	t[num].ans=t[num].lmax=t[num].rmax=0;
	t[num].sum=t[num].len;
	t[num].tag=2;
}

二分

我們可以發現,操作 2 就是先統計一遍 \([l0,r0]\) 中非腦洞的個數。

然后把 \([l0,r0]\) 這段區間全部變成腦洞,再去在 \([l1,r1]\) 這段區間里找到從 \(l0\) 開始算起最右邊腦洞個數 \(\leq[l0,r0]\) 中腦洞的個數。

我們發現腦洞個數是單調遞增的,所以我們可以二分。

我采用的寫法是左閉右開。

void work(){
	int x=query0(1,l0,r0);//統計
	if(x==0)return;//這里要注意,否則我們的邊界就是錯的
	change(1,l0,r0,1);//全部變成腦洞
	int l=l1,r=r1+1;//二分的邊界
	while(l+1<r){//經典寫法
		int mid=(l+r)>>1;//求mid
		if(query1(1,l1,mid)<=x)l=mid;//小於等於
		else r=mid;
	}
	change(1,l1,l,2);//填上去
}

代碼

復雜度 \(O(n \log n + q \log^2 n)\)

#include <bits/stdcpp.h>
#define ls num<<1
#define rs num<<1|1
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &FF){
	T RR=1;FF=0;char CH=getchar();
	for(;!isdigit(CH);CH=getchar())if(CH=='-')RR=-1;
	for(;isdigit(CH);CH=getchar())FF=(FF<<1)+(FF<<3)+(CH^48);
	FF*=RR;
}
template<typename T>inline void write(T x){
	if(x<0)putchar('-'),x*=-1;
	if(x>9)write(x/10);
	putchar(x%10+48);
}
template<typename T>inline void writen(T x){
	write(x);
	puts("");
}
const int N=2e5+10;
struct Tree{
	int l,r,lmax,rmax,sum,tag,len,ans;
}t[N<<2];
int n,m,l0,r0,l1,r1,f;
void pushup(int num){
	t[num].sum=t[ls].sum+t[rs].sum;
	if(t[ls].lmax==t[ls].len)t[num].lmax=t[ls].len+t[rs].lmax;
	else t[num].lmax=t[ls].lmax;
	if(t[rs].rmax==t[rs].len)t[num].rmax=t[rs].len+t[ls].rmax;
	else t[num].rmax=t[rs].rmax;
	t[num].ans=max(max(t[ls].ans,t[rs].ans),t[ls].rmax+t[rs].lmax);
}
void down1(int num){
	t[num].ans=t[num].lmax=t[num].rmax=t[num].len;
	t[num].sum=0;
	t[num].tag=1;
}
void down2(int num){
	t[num].ans=t[num].lmax=t[num].rmax=0;
	t[num].sum=t[num].len;
	t[num].tag=2;
}
void pushdown(int num){
	if(t[num].tag==1){
		down1(ls);down1(rs);
		t[num].tag=0;
	}
	if(t[num].tag==2){
		down2(ls);down2(rs);
		t[num].tag=0;
	}
}
void build(int num,int l,int r){
	t[num].tag=0;
	t[num].l=l;
	t[num].r=r;
	t[num].len=r-l+1;
	if(l==r){
		t[num].sum=1;
		t[num].ans=t[num].lmax=t[num].rmax=0;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	pushup(num);
}
void change(int num,int x,int y,int z){
	if(t[num].l>=x&&t[num].r<=y){
		if(z==1)down1(num);
		if(z==2)down2(num);
		return;
	}
	pushdown(num);
	if(t[ls].r>=x)change(ls,x,y,z);
	if(t[rs].l<=y)change(rs,x,y,z);
	pushup(num);
}
int query0(int num,int x,int y){
	if(t[num].l>=x&&t[num].r<=y)return t[num].sum;
	pushdown(num);
	if(t[ls].r<x)return query0(rs,x,y);
	if(t[rs].l>y)return query0(ls,x,y);
	return query0(ls,x,y)+query0(rs,x,y);
}
int query1(int num,int x,int y){
	if(t[num].l>=x&&t[num].r<=y)return t[num].len-t[num].sum;
	pushdown(num);
	if(t[ls].r<x)return query1(rs,x,y);
	if(t[rs].l>y)return query1(ls,x,y);
	return query1(ls,x,y)+query1(rs,x,y);
}
void work(){
	read(l1);read(r1);
	int x=query0(1,l0,r0);
	if(x==0)return;
	change(1,l0,r0,1);
	int l=l1,r=r1+1;
	while(l+1<r){
		int mid=(l+r)>>1;
		if(query1(1,l1,mid)<=x)l=mid;
		else r=mid;
	}
	change(1,l1,l,2);
}
int query2(int num,int x,int y){
	if(t[num].l>=x&&t[num].r<=y)return t[num].ans;
	pushdown(num);
	if(t[ls].r<x)return query2(rs,x,y);
	if(t[rs].l>y)return query2(ls,x,y);
	return max(max(query2(ls,x,y),query2(rs,x,y)),min(t[ls].rmax,t[rs].l-x)+min(t[rs].lmax,y-t[ls].r));
}
int main(){
	read(n);read(m);
	build(1,1,n);
	while(m--){
		read(f);read(l0);read(r0);
		switch(f){
			case 0:change(1,l0,r0,1);break;
			case 1:work();break;
			case 2:writen(query2(1,l0,r0));break;
		}
	}
	return 0;
}

拓展

這道題目還有更優秀的解法,復雜度可以少掉一個 \(\log\) 也就是變成 \(O(n \log n+q \log{n})\)

我們還是先統計非腦洞個數。

我們寫一個函數 \(fill\) 就是我們用來把腦細胞填入腦洞的函數。我們要填 \(x\) 個腦細胞,會發現有 \(2\) 種情況。

  • \(1\) 種情況是所有腦細胞都填入左子樹。

  • \(2\) 種情況是所有腦細胞不僅把左邊填滿,還有多的放到右子樹。

我們可以根據這個寫代碼:

int fill(int num,int l,int r,int x){//fill的返回值就是剩余的腦細胞數量
	if(x==0)return 0;
	if(t[num].l>=l&&t[num].r<=r&&t[num].sum<=x){
		int s=t[num].sum;//務必要先存起來
		down2(num);
		return x-s;
	}
	pushdown(num);int ans;
	if(t[ls].r<l)ans=fill(rs,l,r,x);
	else if(t[rs].l>r)ans=fill(ls,l,r,x);
		else ans=fill(rs,l,r,fill(ls,l,r,x));
	pushup(num);
	return ans;//答案
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM