CF671E Organizing a Race [* hard]
给定长度为 (n) 的链,第 (i) 个点的点权为 (g_i),边权为 (w_i)
定义一段区间 ([l,r]) 合法,当且仅当从 (l o r~ mathbf{or}~r o l) 的途中点权和减去边权和均大于等于 (0)
现在可以操作 (k) 次,每次给一个点的点权增加 (1)
你指定这 (k) 次操作,求可能可以得到的最长的合法区间的长度。
(nle 10^5,kle 10^9,w_i,g_ile 10^9)
Solution
考虑差分。
对于每个点维护 (L_i) 表示左边的点权和减去边权和。
维护 (R_i) 表示右边的点权和减去边权和。
对于一个区间 ([u,v]) 其合法等价于 (min(L_{u},L_{u+1}...L_{v})ge L_u,min(R_{u},R_{u+1}...R_v)ge R_v)
考虑修改带来了什么,一次修改等价于将一个点右边的 (L_i) 集体增大,将左边的 (R_i) 集体增大。
考虑假设我们直接从左往右走怎么 check,我们会先走到一个非法的点,然后往前这一段都是可以增加的,我们肯定希望每次增加的点都尽可能靠右,假设从 (i) 出发遇到的第一个非法的点为 (mathbf{next}_i),我们肯定会将答案增加在 (mathbf{next}_i-1) 处。
对于 (i=1sim n) 预处理 (mathbf{next}_i)。
不难发现依次不能走的位置恰好就是 (mathbf{next_i,next_{next_i}...})
同时每次增加的量其实也就是 (L_{x}-L_{nxt_x})
接下来考虑走回来的判定,我们想要走回来,也会存在类似的 (mathbf{next}) 的数组,我们可以称为 (mathbf{last}) 数组。
不难发现从 (r) 走回来的过程不需要关注 (l) 的限制了,所以我们必然都是增加在 (r) 端点处。
于是一个区间 ([u,v]) 的答案为 (L_u-min(L_u...L_v)),同时根据 (mathbf{next}) 数组修改前缀 (R) 区间,然后计算 (R_u-min(R_u...R_v))
对于每个 (u),我们连边 (u o mathbf{next}_u),不难发现这一定是一个树状结构,于是我们考虑以 dfs 的角度来考虑答案。
现在定义 (cost(u,v)) 表示仅考虑 (l o r) 时操作区间 ([u,v]) 的花费,不难发现 (cost(u,v)=sum L_x-L_{nxt_x}),同时总花费为 (cost(u,v)+R_v'-min(R_u',R_{u+1}'...R_v'))
考虑转换问题,我们不难发现每次修改现在只剩余左边的 (R_i) 集体增大,但是这样对于解决问题而言并不方便。
考虑进一步转换,不难发现左边的 (R_i) 集体增大等价于右边的 (R_i) 集体减小,如果这样进行操作,我们会发现进行一次修改时,会使得 (cost(l,r)) 集体增大 (1),相应的,(R_i') 也会同时减少 (1),所以 (cost(l,r)+R_i'=R_i) !。
(当然具体实现的细节会发现假设要走到 (x-1) 处,同时操作为操作 (x),那么和走到 (x-1) 处的 cost 是不用累加 (1) 的,但是 (x-1) 处的 (R_i) 是需要减少的,所以这个地方要特殊处理(然而 (R_{x-1}) 确实要减少,但也不可以给 (a_{x-1}) 增加 (1),所以最后的办法)大概只能在 update 的时候额外维护一个 (b) 是否被减的标记,然后递归到右边时在真的减去)
于是一个区间合法等价于 (R_i-min(R_u',R_{u+1} ...R_i')le k)。
区间最小值不利于我们的讨论,其次我们会发现将所有操作操作完不会影响答案(因为差值至少是 (R_i-(R_i-cost(l,r))=cost(l,r)))对于某个 (u) 进行考虑的时候,我们将 ([1,u-1]) 的 (R_i') 均加上 (infty) 即可。
于是问题变成,给定数组 (a,b),每次区间修改 (b),维护最大的 (j) 使得 (a_j-min_{1le ile j} b_ile k),其中 (k) 为常数。
当然,这里的操作顺序是按照树形结构来处理的(所以也附带撤销操作)。
注意到 (k) 为定值,可以先给 (a_j) 集体减 (k),问题变成维护最大的 (j) 使得 (min_{1le ile j} b_i- a_j'ge 0)
设其为 (c_j),我们维护区间 (c_j) 的信息。
对于具体下标的求解,考虑通过线段树二分来解决,每次只需要判定一个区间是否存在合法的值即可,即 (c_j) 的最大值是否大于等于 (0)
于是我们需要维护 (c_j) 的最大值,考虑使用线段树。
考虑设 (c) 表示在考虑仅考虑区间 ([l,r]) 时(即不考虑前缀)(c) 时的 (cmax)
我们需要维护三个信息:
- 区间 (a) 的最小值。
- 区间 (b) 的最小值。
- 仅考虑区间 ([l,r]) 时的右儿子的 (cmax)(注意这个是对于 ([mid+1,r]) 维护的)
那么考虑如何得到这个区间真正的 (cmax),我们发现其实是将之前区间的 (bmin(mathbf{bef})) 丢入了此区间,我们进行分类讨论:
- 如果 (mathbf{bef}ge b_{ls}),那么不难发现右儿子的答案已经维护过了,即 (c_{rs}),于是我们递归处理左儿子。
- 如果 (mathbf{bef}le b_{ls}),那么左儿子的最小值一定由 (mathbf{bef}) 取到,此时的答案即 (mathbf{bef}-min a_{ls})
于是我们可以利用已知信息在 (mathcal O(log n)) 的复杂度维护出正确的 (c) 值。
接下来考虑修改。
对于修改,我们直接修改这个区间的 (c) 和这个区间的 (b) 即可。(打修改标记)
对于查询,先下传修改标记,再递归查询即可。
复杂度 (mathcal O(nlog^2 n))
代码没有写完,目前被一个细节卡住了:
(Code:)
#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int inf = 1e16 ;
const int N = 1e5 + 5 ;
int n, K, Ans, w[N], g[N], L[N], R[N], A[N], B[N], st[N], top, nxt[N] ;
vector<int> G[N] ;
struct node {
int a, b, c, p, tag ;
} tr[N << 3] ;
void add(int x, int k) {
tr[x].tag += k, tr[x].b += k, tr[x].c += k ;
}
void pushmark(int x) {
int &s = tr[x].tag ;
add(ls(x), s), add(rs(x), s), s = 0 ;
}
int query(int x, int l, int r, int bef) {
if( l == r ) {
// printf("return (%lld) [%lld %lld] %lld (%lld %lld %lld)
", x, l, r, bef, tr[x].b - tr[x].p, tr[x].a, tr[x].c ) ;
return min(bef, tr[x].b - tr[x].p) - tr[x].a ;
}
int mid = (l + r) >> 1 ; pushmark(x) ;
if( tr[ls(x)].b <= bef ) return max( query(ls(x), l, mid, bef), tr[x].c ) ;
else return max( bef - tr[ls(x)].a, query(rs(x), mid + 1, r, bef) ) ;
}
void pushup(int x, int l, int r) {
int mid = (l + r) >> 1 ;
tr[x].a = min( tr[ls(x)].a, tr[rs(x)].a ), tr[x].b = min( tr[ls(x)].b, tr[rs(x)].b ),
tr[x].c = max( tr[ls(x)].c, query(rs(x), mid + 1, r, tr[ls(x)].b ) ) ;
}
void build(int x, int l, int r) {
if(l == r) return tr[x] = (node){A[l], B[l], B[l] - A[l], 0, 0}, void() ;
int mid = (l + r) >> 1 ;
build(ls(x), l, mid), build(rs(x), mid + 1, r), pushup(x, l, r) ;
}
void update(int x, int l, int r, int ql, int qr, int k) {
//printf("update %lld [%lld %lld] [%lld %lld] %lld || %lld
", x, l, r, ql, qr, k, tr[x].c ) ;
if( ql <= l && r <= qr ) return add(x, k), void() ;
if( r < ql || l > qr ) return ;
int mid = (l + r) >> 1 ; pushmark(x) ;
update(ls(x), l, mid, ql, qr, k), update(rs(x), mid + 1, r, ql, qr, k),
pushup(x, l, r) ;
//printf("End (%lld) [%lld %lld] %lld
", x, l, r, tr[x].c ) ;
}
void modify(int x, int l, int r, int d, int k) {
if( l == r ) return tr[x].p += k, tr[x].b += k, void() ;
int mid = (l + r) >> 1 ; pushmark(x) ;
if( d <= mid ) modify(ls(x), l, mid, d, k) ;
else modify(rs(x), mid + 1, r, d, k) ;
pushup(x, l, r) ;
}
int Find(int x, int l, int r, int ll) {
if( l == r ) return l ; pushmark(x) ;
int mid = (l + r) >> 1, rw = query(rs(x), mid + 1, r, min( ll, tr[ls(x)].b )) ;
// printf("search : %lld [%lld %lld] %lld (%lld)
", x, l, r, rw, min( ll, tr[ls(x)].b ) ) ;
if( rw >= 0 ) return Find(rs(x), mid + 1, r, min( ll, tr[ls(x)].b )) ;
else return Find(ls(x), l, mid, ll) ;
}
void Dfs(int x, int fa) {
if( fa != n + 1 ) update(1, 1, n, fa, n, L[fa] - L[x]), modify(1, 1, n, fa - 1, L[fa] - L[x]) ;
if( x != 1 ) update(1, 1, n, 1, x - 1, inf ) ;
int rr = Find(1, 1, n, inf) ;
if( x != 1 ) update(1, 1, n, 1, x - 1, -inf ) ;
// printf("now Dfs %lld and %lld (%lld) [%lld]
", x, fa, rr, L[fa] - L[x] ) ;
Ans = max( Ans, rr - x + 1 ) ;
for(int v : G[x]) Dfs(v, x) ;
if( fa != n + 1 ) update(1, 1, n, fa, n, L[x] - L[fa]), modify(1, 1, n, fa - 1, L[x] - L[fa]) ;
}
signed main()
{
n = gi(), K = gi() ;
rep( i, 2, n ) w[i] = gi() ;
rep( i, 1, n ) g[i] = gi() ;
rep( i, 2, n ) L[i] = L[i - 1] + g[i - 1] - w[i] ;
drep( i, 1, n - 1 ) R[i] = R[i + 1] + g[i + 1] - w[i + 1] ;
rep( i, 1, n ) A[i] = R[i] - K, B[i] = R[i] ;
st[++ top] = n + 1, L[n + 1] = -inf, build(1, 1, n) ;
/* rep( i, 1, n ) printf("%lld ", L[i] ) ; puts("") ;
rep( i, 1, n ) printf("%lld ", A[i] ) ; puts("") ;
rep( i, 1, n ) printf("%lld ", B[i] ) ; puts("") ;
*/
drep( i, 1, n ) {
while( L[i] <= L[st[top]] ) -- top ;
// printf("Line %lld :: %lld %lld (%lld)
", i, L[i], L[st[top]], st[top] ) ;
nxt[i] = st[top], st[++ top] = i ;
}
rep( i, 1, n ) G[nxt[i]].push_back(i) ;
Dfs(n + 1, n + 1) ;
cout << Ans << endl ;
return 0 ;
}