[LOJ3038]穿越时空 Bitaro

137 篇文章 1 订阅
11 篇文章 0 订阅

题目

传送门 to LOJ

思路

正向和逆向是类似的,只考虑正向。首先,将 [ L i , R i ) [L_i,R_i) [Li,Ri) 变为 [ L i − i , R i − i ) [L_i-i,R_i-i) [Lii,Rii),这样行走就不耗费时间了,方便了许多。这东西可以看成在坐标系内行走。

有一个显然的贪心做法:尽量往前走,走不了就进行尽可能小的调整,然后继续走。

接下来,对于一段区间,考虑 c o s t − i n i t i a l    l o c a t i o n cost-{\rm initial\;location} costinitiallocation 的图像。如果这一段区间有交集 [ L ′ , R ′ ] [L',R'] [L,R],那么函数为
f 1 ( x ) = { 0 ( 0 ≤ x ≤ R ′ ) x − R ′ ( R ′ < x ) f_1(x)= \begin{cases} 0 & (0\le x\le R')\\ x-R' & (R'<x) \end{cases} f1(x)={0xR(0xR)(R<x)

若这一段区间没有交集,那么函数为
f 2 ( x ) = { c ( 0 ≤ x ≤ b ) x − b + c ( b < x ) f_2(x)= \begin{cases} c & (0\le x\le b)\\ x-b+c & (b<x) \end{cases} f2(x)={cxb+c(0xb)(b<x)

其中 b , c b,c b,c 是与区间有关的常数。第一个挺显然,第二个就需要思考一下了。

任意两个区间的交集非空 ⇒ \Rightarrow 所有区间的交集非空,故必然存在两个区间是相离的。找到最近的两个相离的区间。不妨设前面一个区间为 [ a , b ] [a,b] [a,b],后面一个为 [ c , d ] [c,d] [c,d] 满足 b < c b<c b<c 。那么,对于中间的区间 [ l , r ] [l,r] [l,r] 都满足 l < b < c < r l<b<c<r l<b<c<r 。那么我们下定论,无论走到 [ a , b ] [a,b] [a,b] 的时刻是什么,走出 [ c , d ] [c,d] [c,d] 的时刻都必然是处于 c c c,并且过程中的代价是 0 0 0 。因为抬升到 b b b 都不足以从 c c c 处走出,必然要抬升到 c c c 才行。而抬升到 b b b 同时也保证了过程中所有区间都能穿过。

不妨让 [ a , b ] [a,b] [a,b] 是最靠左的一个(并非是第一个,因为第一个区间可能和所有区间都有交集)。那么第一个与 [ a , b ] [a,b] [a,b] 之间的关系式满足 f 1 ( x ) f_1(x) f1(x) 的关系。只要走出 [ c , d ] [c,d] [c,d] 时必然在 c c c 处,后面的代价就固定了。所以得到
f 2 ( x ) = f 1 ( x ) + [ x ′ > b ] ( x ′ − b ) + c o s t f_2(x)=f_1(x)+[x'>b](x'-b)+cost f2(x)=f1(x)+[x>b](xb)+cost

这里 x ′ x' x f 1 ( x ) f_1(x) f1(x) 出来的位置,即 max ⁡ [ L ′ , min ⁡ ( x , R ′ ) ] \max[L',\min(x,R')] max[L,min(x,R)] 。要么 L ′ < b < R ′ L'<b<R' L<b<R,化简易得
f 2 ( x ) = { c o s t ( x ≤ b ) x − b + c o s t ( b < x ) f_2(x)= \begin{cases} cost & (x\le b)\\ x-b+cost & (b<x) \end{cases} f2(x)={costxb+cost(xb)(b<x)

要么 a < L ′ < R ′ < b a<L'<R'<b a<L<R<b,化简易得
f 2 ( x ) = f 1 ( x ) + c o s t f_2(x)=f_1(x)+cost f2(x)=f1(x)+cost

此二者都是最上方 f 2 ( x ) f_2(x) f2(x) 的形式。于是就证毕了。

当然,如果你追求严谨,还可以再写一写 d < a d<a d<a 即 “前高后低” 的情形,其实都差不多。

经过这一番证明,我们意识到,用线段树维护 f 1 ( x ) f_1(x) f1(x) 或者 f 2 ( x ) f_2(x) f2(x) 即可,因为这是可合并的。复杂度 O [ ( n + q ) log ⁡ n ] \mathcal O[(n+q)\log n] O[(n+q)logn]

代码

从代码的角度也可以证明上面所说的。不过,由于合并是有一定思考难度的,我还是稍微写一下。

f ( x ) = max ⁡ ( x − b , 0 ) + c f(x)=\max(x-b,0)+c f(x)=max(xb,0)+c,考虑合并两个函数 f 1 ( x ) , f 2 ( x ) f_1(x),f_2(x) f1(x),f2(x)(并不代表上文的两种类型),二者区间并集分别为 [ L 1 , R 1 ] , [ L 2 , R 2 ] [L_1,R_1],[L_2,R_2] [L1,R1],[L2,R2]

首先考虑新函数的 b ′ b' b 。如果 [ L 1 , R 1 ] = Ø [L_1,R_1]=\text{\O} [L1,R1]=Ø,即走出来的点是确定的,那么显然 b ′ = b b'=b b=b,因为后面就是个常数的额外代价。否则必定有 L 1 ≤ b ′ ≤ R 1 L_1\le b'\le R_1 L1bR1,因为 x ≤ L 1 x\le L_1 xL1 时代价必定相同,而 x ≥ R 1 x\ge R_1 xR1 时必定要每次增加 1 1 1 的代价。特殊之处就是 f 2 ( x ) f_2(x) f2(x) b 2 b_2 b2,它会让 b ′ b' b 变小成它。形式化地说,
b ′ = max ⁡ ( L 1 , min ⁡ ( R 1 , b 2 ) ) b'=\max(L_1,\min(R_1,b_2)) b=max(L1,min(R1,b2))

再分析一下新函数的出口位置 x ′ x' x 。如果 f 2 ( x ) f_2(x) f2(x) 是出口固定的,那就是它。否则,相当于问你,一个在 x 1 ′ x'_1 x1 位置的点,走 [ L 2 , R 2 ] [L_2,R_2] [L2,R2] 会到哪里?答案也是显然的。
x ′ = max ⁡ ( L 2 , min ⁡ ( R 2 , x 1 ′ ) ) x'=\max(L_2,\min(R_2,x'_1)) x=max(L2,min(R2,x1))

最后考虑新函数的 c ′ c' c 。如果 f 1 ( x ) f_1(x) f1(x) 已经是固定的了,那么 c ′ c' c 就要额外增加中间调整的代价。否则,想想 b ′ b' b 的实际含义:必须要后退到 b ′ b' b 再走出来。这也是为什么 L 1 ≤ b ′ ≤ R 1 L_1\le b'\le R_1 L1bR1 。所以说
c ′ = max ⁡ ( b ′ − b 1 , 0 ) + c 1 + c 2 c'=\max(b'-b_1,0)+c_1+c_2 c=max(bb1,0)+c1+c2

然后就愉快的做完啦!

#include <cstdio> 
#include <iostream>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 300005<<2;
struct ZXY{
	// f(x) = [x>zhe](x-zhe)+cost
	int zhe; int_ cost;
	int ed; // exit
	int L, R; // range
	ZXY(){ toBe(-MaxN,MaxN); }
	void toBe(const int &l,const int &r){
		ed = L = l, zhe = R = r, cost = 0;
	}
	ZXY operator + (const ZXY &t) const {
		ZXY res; res.L = max(L,t.L);
		res.R = min(R,t.R); // get range
		if(res.L <= res.R){
			res.toBe(res.L,res.R); 
			return res;
		}
		if(L > R) res.zhe = zhe;
		else res.zhe = max(L,min(t.zhe,R));
		if(t.L > t.R) res.ed = t.ed;
		else res.ed = max(t.L,min(ed,t.R));
		if(L > R) res.cost = (ed>t.zhe)*(ed-t.zhe);
		else{
			const int &x = res.zhe;
			res.cost = (x>t.zhe)*(x-t.zhe);
		}
		res.cost += cost+t.cost;
		return res;
	}
};

int n;
struct SgTree{
	ZXY v[MaxN];
	void modify(int qid,int ql,int qr,int o=1,int l=1,int r=n-1){
		if(l == r){
			v[o].toBe(ql,qr); return ;
		}
		if(qid <= ((l+r)>>1))
			modify(qid,ql,qr,o<<1,l,(l+r)>>1);
		else modify(qid,ql,qr,o<<1|1,(l+r)/2+1,r);
		v[o] = v[o<<1]+v[o<<1|1];
	}
	ZXY query(int ql,int qr,int o=1,int l=1,int r=n-1){
//printf("query [%d, %d] in [%d, %d]\n",ql,qr,l,r);
		if(ql <= l && r <= qr) return v[o];
		if(qr <= ((l+r)>>1))
			return query(ql,qr,o<<1,l,(l+r)>>1);
		if(ql > ((l+r)>>1))
			return query(ql,qr,o<<1|1,(l+r)/2+1,r);
		return query(ql,qr,o<<1,l,(l+r)>>1)
			+ query(ql,qr,o<<1|1,(l+r)/2+1,r);
	}
};
SgTree fw, bw; // forwards, backwards

int main(){
	n = readint();
	int q = readint();
	for(int i=1; i<=n-1; ++i){
		int L = readint();
		int R = readint()-1;
		fw.modify(i,L-i,R-i);
		bw.modify(n-i,L+i,R+i);
	}
	for(int opt,a,b,c; q; --q){
		opt = readint(), a = readint();
		b = readint(), c = readint();
		if(opt == 1){
			fw.modify(a,b-a,c-1-a);
			bw.modify(n-a,b+a,c-1+a);
//printf("modify %d %d %d\n",a,b-a,c-a-1);
		}
		else if(opt == 2 && a < c){
			ZXY zxy = fw.query(a,c-1);
//printf("zxy = [x-%d]+%lld with [%d,%d] out %d\n",zxy.zhe,zxy.cost,zxy.L,zxy.R,zxy.ed);
			b -= a; a = readint()-c;
			if(zxy.L <= zxy.R)
				zxy.ed = max(zxy.L,min(b,zxy.R));
			printf("%lld\n",(b>zxy.zhe)*(b-zxy.zhe)
				+zxy.cost+(zxy.ed>a)*(zxy.ed-a));
		}
		else if(opt == 2 && a == c){
			a = readint(); // actually d
			printf("%d\n",(b>a)*(b-a));
		}
		else if(opt == 2 && c < a){
			ZXY zxy = bw.query(n-a+1,n-c);
//printf("zxy = [x-%d]+%lld with [%d,%d] out %d\n",zxy.zhe,zxy.cost,zxy.L,zxy.R,zxy.ed);
			b += a-1; a = readint()+c-1;
			if(zxy.L <= zxy.R)
				zxy.ed = max(zxy.L,min(b,zxy.R));
			printf("%lld\n",(b>zxy.zhe)*(b-zxy.zhe)
				+zxy.cost+(zxy.ed>a)*(zxy.ed-a));
		}
	}
	return 0;
}

后记

V V V也是线段树维护函数。非常有趣的技巧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值