题解 [IOI2008]Pyramid Base 金字塔地基

题目链接
题意描述

你有一片\(n\times m\)的网格,其中有\(P\)个障碍\((x1,y1,x2,y2)\),并有一个移除的费用\(C_i\),你有\(B\)的预算用于移除障碍,求修建一座正方形金字塔的最大边长。
\(1\le n,m\le10^6,C_i\le3\times 10^3\)
三组\(Subtask:\)
\(Subtask1:B=0,1\le P\le10^3\)
\(Subtask2:1<B\le2\times 10^9,1\le P\le3\times 10^4\)
\(Subtask3:B=0,1\le P\le4\times 10^5\)

\(\large{Solution:}\)
两类\(Subtask\)区别较大(一种无预算,但要求\(O(n\log n)\),一种有预算,但数据范围游刃有余),考虑分别求解。


\(Algorithm\ \mbox{I}:\)
既然没有预算,那么就可以直接将障碍扫描线,对于\(m\)开一棵线段树,维护最大子段。主函数需要\(O(n)\)的算法,如果知道左边界\(i\),那么我们希望右边界\(j\)尽量大,又因为要求正方形,因此还要保证线段树上最大子段\(\ge j-i+1\),容易想到\(two\ pointers\)不断移动\(j\),在这个过程中取\(\max\)即可。

点击查看代码
namespace Subtask1{
	vector<int>Line[M][2];
	namespace SGT{
		struct Data{int lmax,rmax,Max,Len,Tag,Add;}tr[M<<2];
		void Pushup(int k){
			if(tr[k].Add>=1)return void(tr[k].lmax=tr[k].rmax=tr[k].Max=0);
			if(tr[k].Len==1)return void(tr[k].lmax=tr[k].rmax=tr[k].Max=1);
			tr[k].Len=tr[ls].Len+tr[rs].Len;
			tr[k].lmax=(tr[ls].lmax==tr[ls].Len)?tr[ls].Len+tr[rs].lmax:tr[ls].lmax;
			tr[k].rmax=(tr[rs].rmax==tr[rs].Len)?tr[rs].Len+tr[ls].rmax:tr[rs].rmax;
			tr[k].Max=max(tr[ls].rmax+tr[rs].lmax,max(tr[ls].Max,tr[rs].Max));
		}
		void Build(int k,int l,int r){
			if(l==r){
				tr[k].lmax=tr[k].rmax=tr[k].Max=tr[k].Len=1;
				return;
			}
			Build(ls,l,mid);
			Build(rs,mid+1,r);
			Pushup(k);
		}
		void Modify(int k,int l,int r,int x,int y,int z){
			if(l>=x&&r<=y){
				tr[k].Add+=z;
				Pushup(k);
				return;
			}
			if(x<=mid)Modify(ls,l,mid,x,y,z);
			if(mid<y)Modify(rs,mid+1,r,x,y,z);
			Pushup(k);
		}
	}
	using namespace SGT;
	void Solve(){
		Build(1,1,m);
		for(int i=1;i<=P;i++)Line[a[i].x[0]][0].pb(i),Line[a[i].x[1]][1].pb(i);
		for(int i=1,j=0;i<=n;i++){
			while(tr[1].Max>j-i&&j<=n){
				j++;
				for(auto k:Line[j][0])
					Modify(1,1,m,a[k].y[0],a[k].y[1],1);
			}
			ans=max(ans,j-i);
			for(auto k:Line[i][1])
				Modify(1,1,m,a[k].y[0],a[k].y[1],-1);
		}
		printf("%d\n",ans);
	}
}

\(Algorithm\ \mbox{II}:\)
上来显然先二分答案\(ans\),对于每一行\(i\),考虑维护该行以\((i,j)\)为左下角的正方形,这些正方形已经固定,那么只需要判定是否有一个正方形内所有有交的障碍的预算之和\(\le B\),即维护全局最小值。
一个障碍对正方形的影响范围为\((x1-l+1,y1-l+1,x2,y2)\),可以再次使用扫描线维护。

点击查看代码
namespace Subtask2{
	Obstacle b[N];
	vector<int>Line[M][2];
	namespace SGT{
		struct Data{int Min,Tag;}tr[M<<2];
		void Pushup(int k){tr[k].Min=min(tr[ls].Min,tr[rs].Min);}
		void Update(int k,int v){tr[k].Tag+=v;tr[k].Min+=v;}
		void Pushdown(int k){
			if(tr[k].Tag!=0){
				Update(ls,tr[k].Tag);
				Update(rs,tr[k].Tag);
				tr[k].Tag=0;
			}
		}
		void Build(int k,int l,int r){
			tr[k].Min=tr[k].Tag=0;
			if(l==r)return;
			Build(ls,l,mid);
			Build(rs,mid+1,r);
		}
		void Modify(int k,int l,int r,int x,int y,int z){
			if(l>=x&&r<=y){
				Update(k,z);
				return;
			}
			Pushdown(k);
			if(x<=mid)Modify(ls,l,mid,x,y,z);
			if(mid<y)Modify(rs,mid+1,r,x,y,z);
			Pushup(k);
		}
	}
	using namespace SGT;
	bool check(int x){
		Build(1,1,m-x+1);
		for(int i=1;i<=n-x+1;i++)Line[i][0].clear(),Line[i][1].clear();
		for(int i=1;i<=P;i++){
			b[i]=a[i];
			b[i].x[0]=max(b[i].x[0]-x+1,1);
			b[i].y[0]=max(b[i].y[0]-x+1,1);
			b[i].x[1]=min(b[i].x[1],n-x+1);
			b[i].y[1]=min(b[i].y[1],m-x+1);
			if(b[i].x[0]>b[i].x[1]||b[i].y[0]>b[i].y[1])continue;
			Line[b[i].x[0]][0].pb(i);
			Line[b[i].x[1]][1].pb(i);
		}
		for(int i=1;i<=n-x+1;i++){
			for(auto j:Line[i][0])
				Modify(1,1,m-x+1,b[j].y[0],b[j].y[1],b[j].C);
			if(tr[1].Min<=B)return true;
			for(auto j:Line[i][1])
				Modify(1,1,m-x+1,b[j].y[0],b[j].y[1],-b[j].C);
		}
		return false;
	}
	void Solve(){
		int l=1,r=min(n,m),ans=0;
		while(l<=r){
			if(check(mid))ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
}
posted @ 2022-06-28 15:28  pidan007  阅读(30)  评论(0编辑  收藏  举报