把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【LOJ3038】「JOISC 2019 Day3」穿越时空 Bitaro(线段树)

点此看题面

  • 给定一条长度为\(n\)的链,你只能在\(L_i\sim R_i-1\)间的某一时刻从\(i\)出发走到\(i+1\),走每条边都花费\(1\)单位时间。
  • 一次操作可以让时间倒流\(1\)秒。
  • \(q\)次操作,分为两种:修改某一条边的\(L,R\);询问在\(B\)时刻从\(A\)出发,在\(D\)时刻之前到达\(C\)至少需要操作多少次。
  • \(n,q\le3\times10^5\)

时间“静止”

考虑我们每走一步时间会加\(1\),这是一个挺烦人的条件。

对于从右向左的询问,我们只要之后把整个序列翻转一下另做一次就好了,因此可以认为所有询问都是从左向右的。

然后我们把每个\(L_i,R_i\)都减去\(i\),这样一来相当于让时间“静止”了。

区间的合并

考虑连续经过两个区间\([l_1,r_1],[l_2,r_2]\)

如果\([l_1,r_1]\cap[l_2,r_2]\not=\emptyset\),那么其实就等价于经过一个区间\([l_1,r_1]\cap[l_2,r_2]\)

否则,如果\(r_1<l_2\)则必然会先凝聚到\(r_1\)一个点然后走到\(l_2\),如果\(l_1>r_2\)则先凝聚到\(l_1\)然后走到\(r_2\),且从此我们都将是一个点。

所以实际上只有两种情况,分别可以用一个二元组\((l,r)\)(区间\([l,r]\))和一个三元组\((x,y,v)\)(先凝聚成\(x\)进入,最后变成\(y\),一共操作了\(v\)次)。

由于这个东西满足结合律,加上有单点修改,我们选择线段树维护。

询问时只要求出\(A\sim C-1\)的等价元组,然后在前后分别加上\((B-A,B-A)\)\((D-C,D-C)\)两个二元组即可。

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300000
#define LL long long
using namespace std;
int n,Qt,L[N+5],R[N+5],l[N+5],r[N+5];LL ans[N+5];struct Q {int op,A,B,C,D;}q[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[30],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
struct Data
{
	int x,y;LL v;I Data(CI a=0,CI b=0,Con LL& c=-1):x(a),y(b),v(c){}
	I friend Data operator + (Con Data& A,Con Data& B)//元组的合并
	{
		if(~A.v&&~B.v) return Data(A.x,B.y,A.v+B.v+max(A.y-B.x,0));//三元组合并
		if(~A.v) return Data(A.x,max(min(A.y,B.y),B.x),A.v+max(A.y-B.y,0));//三元组+二元组
		if(~B.v) return Data(min(max(A.x,B.x),A.y),B.y,B.v+max(A.x-B.x,0));//二元组+三元组
		if(max(A.x,B.x)<=min(A.y,B.y)) return Data(max(A.x,B.x),min(A.y,B.y),-1);//二元组合并:存在交集
		return A.y<B.x?Data(A.y,B.x,0):Data(A.x,B.y,A.x-B.y);//不存在交集,变成三元组
	}
};
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n-1,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) (V[x]=V[x<<1]+V[x<<1|1])
		Data V[N<<2];
	public:
		I void Bd(PT)
		{
			if(l==r) return (void)(V[rt]=Data(::l[l],::r[l]));RI mid=l+r>>1;Bd(LT),Bd(RT),PU(rt);
		}
		I void U(CI x,Con Data& v,PT)//单点修改
		{
			if(l==r) return (void)(V[rt]=v);RI mid=l+r>>1;x<=mid?U(x,v,LT):U(x,v,RT),PU(rt);
		}
		I Data Q(CI L,CI R,PT)//区间查询
		{
			if(L<=l&&r<=R) return V[rt];RI mid=l+r>>1;if(R<=mid) return Q(L,R,LT);
			if(L>mid) return Q(L,R,RT);return Q(L,mid,LT)+Q(mid+1,R,RT);
		}
}S;
I void Solve()
{
	if(n==1) return;RI i;for(i=1;i^n;++i) l[i]=L[i]-i,r[i]=R[i]-1-i;S.Bd();//特判n=1;给L,R减去i使时间静止
	for(i=1;i<=Qt;++i) q[i].op==1?(S.U(q[i].A,Data(q[i].B-q[i].A,q[i].C-1-q[i].A)),0)//单点修改
		:q[i].A<q[i].C&&(ans[i]=(Data(q[i].B-q[i].A,q[i].B-q[i].A,-1)+S.Q(q[i].A,q[i].C-1)+Data(q[i].D-q[i].C,q[i].D-q[i].C)).v);//区间查询,前后加上两个二元组
}
int main()
{
	RI i;for(read(n),read(Qt),i=1;i^n;++i) read(L[i]),read(R[i]);
	for(i=1;i<=Qt;++i) read(q[i].op),read(q[i].A),read(q[i].B),read(q[i].C),q[i].op==2&&(read(q[i].D),0);
	for(Solve(),reverse(L+1,L+n),reverse(R+1,R+n),i=1;i<=Qt;++i) q[i].op==1?q[i].A=n-q[i].A:(q[i].A=n-q[i].A+1,q[i].C=n-q[i].C+1);//翻转
	for(Solve(),i=1;i<=Qt;++i) q[i].op==2&&(writeln(q[i].A^q[i].C?ans[i]:max(q[i].B-q[i].D,0)),0);return clear(),0;//输出前特判单点
}
posted @ 2021-05-30 20:17  TheLostWeak  阅读(145)  评论(2编辑  收藏  举报