题目
思路
正向和逆向是类似的,只考虑正向。首先,将 [ L i , R i ) [L_i,R_i) [Li,Ri) 变为 [ L i − i , R i − i ) [L_i-i,R_i-i) [Li−i,Ri−i),这样行走就不耗费时间了,方便了许多。这东西可以看成在坐标系内行走。
有一个显然的贪心做法:尽量往前走,走不了就进行尽可能小的调整,然后继续走。
接下来,对于一段区间,考虑
c
o
s
t
−
i
n
i
t
i
a
l
l
o
c
a
t
i
o
n
cost-{\rm initial\;location}
cost−initiallocation 的图像。如果这一段区间有交集
[
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)={0x−R′(0≤x≤R′)(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)={cx−b+c(0≤x≤b)(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](x′−b)+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)={costx−b+cost(x≤b)(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(x−b,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
L1≤b′≤R1,因为
x
≤
L
1
x\le L_1
x≤L1 时代价必定相同,而
x
≥
R
1
x\ge R_1
x≥R1 时必定要每次增加
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
L1≤b′≤R1 。所以说
c
′
=
max
(
b
′
−
b
1
,
0
)
+
c
1
+
c
2
c'=\max(b'-b_1,0)+c_1+c_2
c′=max(b′−b1,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也是线段树维护函数。非常有趣的技巧。