CF671E
CF671E Organizing a Race [* hard]
给定长度为 \(n\) 的链,第 \(i\) 个点的点权为 \(g_i\),边权为 \(w_i\)
定义一段区间 \([l,r]\) 合法,当且仅当从 \(l\to r~ \mathbf{or}~r\to l\) 的途中点权和减去边权和均大于等于 \(0\)
现在可以操作 \(k\) 次,每次给一个点的点权增加 \(1\)
你指定这 \(k\) 次操作,求可能可以得到的最长的合法区间的长度。
\(n\le 10^5,k\le 10^9,w_i,g_i\le 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=1\sim 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\to \mathbf{next}_u\),不难发现这一定是一个树状结构,于是我们考虑以 dfs 的角度来考虑答案。
现在定义 \(cost(u,v)\) 表示仅考虑 \(l\to 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_{1\le i\le j} b_i\le k\),其中 \(k\) 为常数。
当然,这里的操作顺序是按照树形结构来处理的(所以也附带撤销操作)。
注意到 \(k\) 为定值,可以先给 \(a_j\) 集体减 \(k\),问题变成维护最大的 \(j\) 使得 \(\min_{1\le i\le j} b_i- a_j'\ge 0\)
设其为 \(c_j\),我们维护区间 \(c_j\) 的信息。
对于具体下标的求解,考虑通过线段树二分来解决,每次只需要判定一个区间是否存在合法的值即可,即 \(c_j\) 的最大值是否大于等于 \(0\)
于是我们需要维护 \(c_j\) 的最大值,考虑使用线段树。
考虑设 \(c\) 表示在考虑仅考虑区间 \([l,r]\) 时(即不考虑前缀)\(c\) 时的 \(c\max\)
我们需要维护三个信息:
- 区间 \(a\) 的最小值。
- 区间 \(b\) 的最小值。
- 仅考虑区间 \([l,r]\) 时的右儿子的 \(c\max\)(注意这个是对于 \([mid+1,r]\) 维护的)
那么考虑如何得到这个区间真正的 \(c\max\),我们发现其实是将之前区间的 \(b\min(\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(n\log^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)\n", 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\n", 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\n", 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)\n", 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]\n", 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)\n", 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 ;
}