• CF671E


    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)

    我们需要维护三个信息:

    1. 区间 (a) 的最小值。
    2. 区间 (b) 的最小值。
    3. 仅考虑区间 ([l,r]) 时的右儿子的 (cmax)(注意这个是对于 ([mid+1,r]) 维护的)

    那么考虑如何得到这个区间真正的 (cmax),我们发现其实是将之前区间的 (bmin(mathbf{bef})) 丢入了此区间,我们进行分类讨论:

    1. 如果 (mathbf{bef}ge b_{ls}),那么不难发现右儿子的答案已经维护过了,即 (c_{rs}),于是我们递归处理左儿子。
    2. 如果 (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 ;
    }
    
  • 相关阅读:
    gbk学习笔记
    在freebsd下编译nodejs,出现无法找到execinfo.h头文件的错误
    php 截取GBK文档某个位置开始的n个字符
    linux下,phpstorm配置oracle jdk
    gb2312学习笔记
    freebsd下vim默认的vi操作方式太难用,可通过启用vim自带配置文件解决
    freebsd通过ssh远程登陆慢,用户认证时间长解决办法
    php输出全部gb2312编码内的汉字
    visibility:hidden 与 display:none 的区别
    java 实现文件/文件夹复制、删除、移动(二)
  • 原文地址:https://www.cnblogs.com/Soulist/p/13687570.html
Copyright © 2020-2023  润新知