[HNOIAHOI2018] 转盘(线段树维护单调栈)

problem

洛谷链接

solution

结论:最优方案中一定有一种是全程不停的。

断环成链,接一个 [ 1 , n ] [1,n] [1,n] 在后面形成 2 n 2n 2n 的序列,同时将时间戳逆过来。

转化成:在 t t t 时刻从某个位置 i ∈ [ n , 2 n ) i\in[n,2n) i[n,2n) 开始往前取物品,时刻 − 1 -1 1,物品 j j j t j t_j tj 时刻消失,要求标记所有的物品。最小化这个 t t t

t t t 时刻开始从 i i i 位置到物品 j j j,需要 i − j i-j ij 的时间,即 ∀ j ∈ ( i − n , i ]   t − ( i − j ) ≥ T j \forall_{j\in(i-n,i]}\ t-(i-j)\ge T_j j(in,i] t(ij)Tj

显然全程不停歇是更好的。

t − ( i − j ) ≥ t j ⇒ t ≥ t j − j + i , j ∈ ( i − n , i ] ⇒ t min ⁡ = max ⁡ { t j − j } + i , j ∈ ( i − n , i ] t-(i-j)\ge t_j\Rightarrow t\ge t_j-j+i,j\in(i-n,i]\Rightarrow t_{\min}=\max\{t_j-j\}+i,j\in(i-n,i] t(ij)tjttjj+i,j(in,i]tmin=max{tjj}+i,j(in,i]

考虑枚举 i ∈ [ n , 2 n ) i\in[n,2n) i[n,2n),算出 [ i ] t min ⁡ [i]t_{\min} [i]tmin 整体取 min ⁡ \min min 才是最终答案,每个 i i i 的计算都要 n log ⁡ n n\log n nlogn

a i = T i − i a_i=T_i-i ai=Tii,则 a n s = min ⁡ n ≤ i < 2 n { max ⁡ i − n < j ≤ i { a j } + i } ans=\min_{n\le i< 2n}\big\{\max_{i-n<j\le i}\{a_j\}+i\big\} ans=minni<2n{maxin<ji{aj}+i}

i i i 替换 i + n − 1 i+n-1 i+n1,有 a n s = min ⁡ 1 ≤ i ≤ n { max ⁡ i ≤ j ≤ i + n − 1 { a j } + i } + n − 1 ans=\min_{1\le i\le n}\big\{\max_{i\le j\le i+n-1}\{a_j\}+i\big\}+n-1 ans=min1in{maxiji+n1{aj}+i}+n1

因为 a i = T i − i > a i + n = T i − ( i + n ) a_i=T_i-i>a_{i+n}=T_{i}-(i+n) ai=Tii>ai+n=Ti(i+n),所以稍微扩大 j j j 范围对答案无影响。

a n s = min ⁡ 1 ≤ i ≤ n { max ⁡ i ≤ j ≤ 2 n { a j } + i } + n − 1 ans=\min_{1\le i\le n}\big\{\max_{i\le j\le 2n}\{a_j\}+i\big\}+n-1 ans=min1in{maxij2n{aj}+i}+n1

i i i 处理一个后缀最大值就能得到答案,到此也只是扔了个 l o g log log 并没起到多大优化, O ( n m ) O(nm) O(nm) 仍无法接受。

i i i 不行转过来考虑 j j j,考虑每个 j j j 的答案是怎样的。(特别地, a 0 = i n f a_0=inf a0=inf

  • 如果 j j j 是后缀最大值点。

    对于 j j j,往前找到第一个 a k > a j a_k>a_j ak>aj k k k i ∈ ( k , n ] i\in(k,n] i(k,n] 都是 j j j 处取 max ⁡ \max max,当 i = k + 1 i=k+1 i=k+1 时才满足外层的 min ⁡ \min min 操作,因此 j j j 的贡献应为 a j + k + 1 a_j+k+1 aj+k+1

    i i i 的限制可以反推得到 k < n k<n k<n

  • 如果 j j j 不是,那答案仍是后面某个点产生的。

  • 最后就是这些 j j j 贡献取 min ⁡ \min min

基于这样的分类,发现可以从后往前维护 a j a_j aj 的一个单调递增栈。

假设单调栈内的元素为 s 0 = 0 < s 1 < . . . < s x s_0=0<s_1<...<s_x s0=0<s1<...<sx

a n s = min ⁡ 1 ≤ i ≤ x { a s i + s i − 1 + 1 } + n − 1 = min ⁡ 1 ≤ i ≤ x { a s i + s i − 1 } + n ans=\min_{1\le i\le x}\big\{a_{s_i}+s_{i-1}+1\big\}+n-1=\min_{1\le i\le x}\big\{a_{s_i}+s_{i-1}\big\}+n ans=min1ix{asi+si1+1}+n1=min1ix{asi+si1}+n,要求 s i − 1 < n s_{i-1}<n si1<n

用线段树来维护单调栈楼房重建,具体合并两个区间单调栈。

  • 右子树答案直接拿。
  • 用右子树最大值在做子树内找到最靠近右子树的第一个比其大的位置返回。
  • 对该位置前面的取整体最小值。

最优位置 j j j 要满足 s j − 1 < n s_{j-1}<n sj1<n 所以要记录这个询问的结果位置。

但最终答案实际上就是 i ∈ ( n , 2 n ] i\in(n,2n] i(n,2n] 的最大值在 [ 1 , n ] [1,n] [1,n] 里面二分找 j j j 执行上述流程中得到的。

看似是维护 a i ( 1 ≤ i ≤ 2 n ) a_i(1\le i\le 2n) ai(1i2n) 的最大值,事实上 i ∈ [ n , 2 n ) i\in[n,2n) i[n,2n) 的最大值 a i = T i − i a_i=T_i-i ai=Tii,就等于 i − n ∈ [ 1 , n ) i-n\in[1,n) in[1,n) 的最大值 a i − n = T i − n − ( i − n ) = T i − i + n a_{i-n}=T_{i-n}-(i-n)=T_i-i+n ain=Tin(in)=Tii+n 减去 n n n

所以线段树只需要维护 [ 1 , n ] [1,n] [1,n] 的最大值信息即可,再在 [ 1 , n ] [1,n] [1,n] 里面查。

时间复杂度 O ( ( n + Q ) log ⁡ n 2 ) O((n+Q)\log n^2) O((n+Q)logn2)

code

#include <bits/stdc++.h>
using namespace std;
#define inf 0x7f7f7f7f
#define maxn 100005
int n, m, p;
int t[maxn], Max[maxn << 2], Ans[maxn << 2];

#define lson now << 1
#define rson now << 1 | 1
#define mid (l + r >> 1)

int query( int now, int l, int r, int x ) {
    if( l == r ) return Max[now] > x ? x + l : inf;
    if( Max[rson] <= x ) return query( lson, l, mid, x );
    else return min( Ans[now], query( rson, mid + 1, r, x ) );
}

void pushup( int now, int l, int r ) {
    Max[now] = max( Max[lson], Max[rson] );
    Ans[now] = query( lson, l, mid, Max[rson] );
}

void build( int now, int l, int r ) {
    if( l == r ) { Max[now] = t[l] - l; return; }
    build( lson, l, mid );
    build( rson, mid + 1, r );
    pushup( now, l, r );
}

void modify( int now, int l, int r, int pos ) {
    if( l == r ) { Max[now] = t[pos] - pos; return; }
    if( pos <= mid ) modify( lson, l, mid, pos );
    else modify( rson, mid + 1, r, pos );
    pushup( now, l, r );
}

int main() {
    scanf( "%d %d %d", &n, &m, &p );
    for( int i = 1;i <= n;i ++ ) scanf( "%d", &t[i] );
    build( 1, 1, n );
    int ans = query( 1, 1, n, Max[1] - n ) + n;
    printf( "%d\n", ans );
    while( m -- ) {
        int x, y;
        scanf( "%d %d", &x, &y );
        if( p ) x ^= ans, y ^= ans;
        t[x] = y, modify( 1, 1, n, x );
        ans = query( 1, 1, n, Max[1] - n ) + n;
        printf( "%d\n", ans );
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值