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

点此看题面

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

时间“静止”

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

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

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

区间的合并

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

如果([l_1,r_1]cap[l_2,r_2] ot=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)次)。

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

询问时只要求出(Asim 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('
');}
}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;//输出前特判单点
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ3038.html