P7560 [JOISC 2021 Day1] フードコート

发布时间 2023-11-07 07:53:30作者: WrongAnswer_90

P7560 [JOISC 2021 Day1] フードコート

更好的阅读体验

神奇的换维扫描线。

直接做感觉十分困难,因为是区间操作,并且清空到 \(0\) 之后就不再清空,查询不好处理到底是第几个人。但是如果知道了这个点的操作序列,问题就简单很多。询问是单点查询并且可以离线。综合上面所有因素,考虑换一维扫描线:对序列维扫描,横轴为序列维,纵轴为操作序列即时间维。

一个操作 \(1,2\),差分后,看成在 \(L\) 处出现,\(R+1\) 处消失。现在考虑对于一个序列上的位置,求出了它对应的操作序列之后如何处理。

虽然一个点可能被清空多次,但是我们只关心最后一次清空的位置和之后的操作。有一个性质:如果存在清空,则最后一次清空的位置一定是操作序列的前缀和最小值,原因显然:前一次清空和本次清空之间的和必须为负数才能再次产生清空。

理一下思路:需要维护前缀前缀和最小值,支持动态增删,和找出一个操作位置之后的减的和,找出加的和大于一个值的第一个位置。可以用线段树维护时间维。加、删就直接在对应位置上加减。

对于查询,找出它前面前缀和最小值的位置。如果前缀和最小值非负,则证明没有出现清空操作。找出前面一共走了多少人,然后线段树上二分到对应的加人操作。

如果前缀和最小值小于 \(0\),则证明在该位置上清空到了 \(0\) 并且是最后一个清空到 \(0\) 的位置,在它之后像上面一样计算。

复杂度 \(\mathcal O(q\log q)\)

	int n,m,q,typ[250010],ans[250010],col[250010];
	struct Node{int pos,x,opt;};
	vector<Node> ve[250010];
	namespace Segment
	{
		struct NNode{int l,r,sum,add,minn,mini;}t[1000010];
		inline void update(int p)
		{
			t[p].minn=min(t[p*2].minn,t[p*2].sum+t[p*2+1].minn);
			if(t[p*2].minn==t[p].minn)t[p].mini=t[p*2].mini;
			else t[p].mini=t[p*2+1].mini;
			t[p].sum=t[p*2].sum+t[p*2+1].sum;
			t[p].add=t[p*2].add+t[p*2+1].add;
		}
		void build(int p,int l,int r)
		{
			t[p].l=l,t[p].r=r;
			if(l==r)return t[p].mini=l,void();
			int mid=l+((r-l)>>1);
			build(p*2,l,mid),build(p*2+1,mid+1,r),update(p);
		}
		void modify(int p,int x,int y,int typ=0)
		{
			if(t[p].l==t[p].r)return t[p].sum+=y,typ?t[p].add+=y:0,t[p].minn=min(0ll,t[p].sum),void();
			if(x<=t[p*2].r)modify(p*2,x,y,typ);else modify(p*2+1,x,y,typ);
			update(p);
		}
		NNode ask(int p,int x)
		{
			if(x>=t[p].r)return t[p];
			if(x<=t[p*2].r)return ask(p*2,x);
			NNode nd=ask(p*2+1,x),ans={0,0,nd.sum+t[p*2].sum,0,min(t[p*2].minn,t[p*2].sum+nd.minn),0};
			if(ans.minn==t[p*2].minn)ans.mini=t[p*2].mini;else ans.mini=nd.mini;
			return ans;
		}
		int query(int p,int st,int &x)
		{
			if(st<=t[p].l)
			{
				if(t[p].add<x)return x-=t[p].add,0;
				if(t[p].l==t[p].r)return t[p].l;
				if(t[p*2].add>=x)
				return query(p*2,st,x);
				x-=t[p*2].add;
				return query(p*2+1,st,x);
			}
			if(st<=t[p*2].r)
			{
				int t=query(p*2,st,x);
				if(t)return t;
			}
			return query(p*2+1,st,x);
		}
		void print(int p)
		{
			if(t[p].l==t[p].r)write(t[p].l),write(t[p].r),write(t[p].sum),write(t[p].add),write(t[p].minn),write(t[p].mini,'\n');
			if(t[p].l==t[p].r)return;
			print(p*2),print(p*2+1);
		}
		int asksum(int p,int l,int r)
		{
			if(l<=t[p].l&&r>=t[p].r)return t[p].sum-t[p].add;
			int s=0;
			if(l<=t[p*2].r)s+=asksum(p*2,l,r);
			if(r>t[p*2].r)s+=asksum(p*2+1,l,r);
			return s;
		}
	}
	using namespace Segment;
	inline void mian()
	{
		read(n,m,q);int opt,x,y,z,t;
		for(int i=1;i<=q;++i)
		{
			read(opt);
			if(opt==1)read(x,y,z,t),col[i]=z,ve[x].eb((Node){i,t,1}),ve[y+1].eb((Node){i,-t,1});
			else if(opt==2)read(x,y,z),ve[x].eb((Node){i,z,2}),ve[y+1].eb((Node){i,-z,2});
			else read(x,y),typ[i]=1,ve[x].eb((Node){i,y,3});
		}
		build(1,1,q);
		for(int i=1;i<=n;++i)
		{
			for(auto nd:ve[i])
			{
				if(nd.opt==1)modify(1,nd.pos,nd.x,1);
				else if(nd.opt==2)modify(1,nd.pos,-nd.x);
				else
				{
					NNode nd2=ask(1,nd.pos);
					if(nd2.minn>=0)
					{
						int cut=-asksum(1,1,nd.pos)+nd.x;
						ans[nd.pos]=query(1,1,cut);
					}
					else 
					{
						int cut=-asksum(1,nd2.mini+1,nd.pos)+nd.x;
						ans[nd.pos]=query(1,nd2.mini+1,cut);
					}
				}
			}
		}
		for(int i=1;i<=q;++i)if(typ[i])write(ans[i]>i?0:col[ans[i]],'\n');
	}