【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;//输出前特判单点
}
待到再迷茫时回头望,所有脚印会发出光芒