Luogu P5103 「JOI 2016 Final」断层 树状数组or线段树+脑子
太神仙了这题。。。
原来的地面上升,可以倒着操作(时光倒流),转化为地面沉降,最后的答案就是每个点的深度。
下面的1,2操作均定义为向下沉降(与原题意的变换相反);
首先这个题目只会操作前缀和后缀,并且只会把前缀中的数(纵坐标)变小(2操作),后缀中的数(横坐标)变大(1操作),所以具有单调性,可以进行二分。(括号中含义的解释见下)
先把整个坐标系旋转$45$度(逆时针为例),操作1即纵坐标$y>=xi$的点都会往右走$2*l$,横坐标$+2*l$,纵坐标不变,由于有单调性,只会操作后缀;操作2即横坐标$x<=xi$的点都会往下走$2*l$,纵坐标$-2*l$,横坐标不变,由于有单调性,只会操作前缀。
所以二分一下实际坐标就好了。。注意最后计算深度是$(x-y)/2$
我的这种二分需要维护一个$mx$区间最大值,二分时看一眼左右子树的$mx$,然后决定向哪一棵子树递归。
#include<cstdio> #include<iostream> #define ls (tr<<1) #define rs (tr<<1|1) #define ll long long #define R register ll const int N=200010,Inf=0x3f3f3f3f; using namespace std; char B[1<<15],*S=B,*T=B,ch; #define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++) inline int g() { R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix; } int n,m; struct node {int x,d,l;}q[N]; ll MX[2][N<<2],TG[2][N<<2]; #define mx MX[c] #define tg TG[c] inline void build(int c,int tr,int l,int r) { if(l==r) {mx[tr]=l; return ;} R md=l+r>>1; build(c,ls,l,md),build(c,rs,md+1,r); mx[tr]=max(mx[ls],mx[rs]); } inline void spread(int c,int tr) { if(!tg[tr]) return ; tg[ls]+=tg[tr],tg[rs]+=tg[tr],mx[ls]+=tg[tr],mx[rs]+=tg[tr]; tg[tr]=0; } ll pos; inline void fx(int tr,int l,int r,int k) { if(l==r) {if(MX[0][tr]<=k) pos=max(pos,(ll)l); return ;} spread(0,tr); R md=l+r>>1; if(MX[0][ls]<=k) pos=max(pos,md),fx(rs,md+1,r,k); else fx(ls,l,md,k); } inline void fy(int tr,int l,int r,int k) { if(l==r) {if(MX[1][tr]>k) pos=min(pos,(ll)l); return ;} spread(1,tr); R md=l+r>>1; if(MX[1][ls]<=k) fy(rs,md+1,r,k); else fy(ls,l,md,k); } inline void add(int c,int tr,int l,int r,int LL,int RR,int d) { if(LL<=l&&r<=RR) {mx[tr]+=d,tg[tr]+=d; return ;} spread(c,tr); R md=l+r>>1; if(LL<=md) add(c,ls,l,md,LL,RR,d); if(RR>md) add(c,rs,md+1,r,LL,RR,d); mx[tr]=max(mx[ls],mx[rs]); } ll p[2][N]; inline void calc(int c,int tr,int l,int r) { if(l==r) {p[c][l]=mx[tr]; return ;} spread(c,tr); R md=l+r>>1; calc(c,ls,l,md),calc(c,rs,md+1,r); } signed main() { freopen("geologic.in","r",stdin); freopen("geologic.out","w",stdout); n=g(),m=g(); for(R i=1;i<=m;++i) q[i].x=g(),q[i].d=g(),q[i].l=g(); build(0,1,1,n),build(1,1,1,n); for(R i=m;i>=1;--i) { if(q[i].d==1) { pos=0; fx(1,1,n,q[i].x); if(pos) add(1,1,1,n,1,pos,-2*q[i].l); } else { pos=Inf; fy(1,1,n,q[i].x); if(pos!=Inf) add(0,1,1,n,pos,n,2*q[i].l); } //cerr<<pos<<endl; } calc(0,1,1,n),calc(1,1,1,n); for(R i=1,ans;i<=n;++i) ans=(p[0][i]-p[1][i])/2,printf("%lld\n",ans); }
这还有一个不旋转坐标的,具体的就是类似直接模拟,但是难度在如何二分位置;
想一想发现:这不是直线方程么。。。
所以还是分别维护横纵坐标,但是二分条件改成$y>=x-xi$即$x-y<=xi$或$y>=-x+xi$即$x+y>=xi$;
#include<cstdio> #include<iostream> #define ll long long #define R register ll const int M=262145; char B[1<<15],*S=B,*T=B; #define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++) using namespace std; inline int g() { R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix; } ll x[M],y[M],ans[M]; inline int fx(ll d) { R pos=0,t; for(R i=17;~i;--i) if((t=x[pos+(1<<i)]-y[pos+(1<<i)])<=d) pos+=(1<<i),d-=t; return pos; } inline int fy(ll d) { R pos=0,t; for(R i=17;~i;--i) if((t=x[pos+(1<<i)]+y[pos+(1<<i)])<=d) pos+=(1<<i),d-=t; return pos; } int n,m; inline void add(int pos,int incx,int incy) {for(;pos<M;pos+=pos&-pos) x[pos]+=incx,y[pos]+=incy;} struct node {int x,d,l;} q[M]; signed main() { freopen("geologic.in","r",stdin); freopen("geologic.out","w",stdout); n=g(),m=g(); for(R i=1;i<=n;++i) add(i,1,0); for(R i=1;i<=m;++i) q[i].x=g(),q[i].d=g(),q[i].l=g(); for(R i=m;i;--i) if(q[i].d==1) { R pos=fx(q[i].x); if(pos) add(1,-q[i].l,-q[i].l),add(pos+1,q[i].l,q[i].l); } else { R pos=fy(q[i].x); if(pos<n) add(pos+1,q[i].l,-q[i].l);} for(R i=1;i<=n;++i) { ans[i]=ans[i-(i&-i)]+y[i]; printf("%lld\n",-ans[i]); } }
2019.06.01 June