HNOI2018转盘

题意:

给一个大小为 \(n\) 的环,每个点拥有一个出现时间 \(t_i\),你初始可以选定任何一个点作为起点,然后每一时刻你可以向下走一格或停留在原地,若当前时间\(\rm{T}\ge\) \(t_i\)则你可以标记其,问标记所有点最少耗时\(\rm T\),支持修改。

\(\rm Sol:\)

考虑转化题意,反向考虑,假设当前在某一个位置\(x\)且时间为\(\rm T\),每一时刻你可以向前走一步或者停留在原地,然后时间\(\rm T-1\),每个点在时间比\(t_i\)小的时候便会消失,若你到达这个点时有\(\rm T \ge\) \(t_i\),那么你便可以标记此物品,求标记完所有物品的最小时间

这样可以发现最优决策一定是\(keep~going\),反过来即初始在某一个点停留一段时间然后一直往前走直到标记完所有点

由于环非常不好处理,所以可以考虑断环为链,接下来会发现某一个作为起点的答案为:

\[T+n-1\]

且有:

\[T+(i-x)\ge t_i\]

\[T\ge t_i-i+x\]

所以我们可以知道\(T\)为:

\[\max_{i=x}^{x+n-1}(t_i-i+x)\]

所以耗时为:

\[\max_{i=x}^{x+n-1}(t_i-i)+x+n-1\]

于是我们可以通过枚举\(x\)来得到一个\(O(n\log n)\)的做法

接下来注意到对于断环为链之后对于\(i\)\(i+n\)而言显然有:\(t_i-i>t_i-i-n\)

所以我们可以将限制稍微变宽改为:

\[\max_{i=x}^{2n}(t_i-i)+x+n\]

于是问题转化为求解一个\(1\le x\le n\)

即:

\[x+\max_{i=x}^{2n}(t_i-i)\]

的最小值

如果令\(f_x=\max_{i=x}^{2n}(t_i-i)\)

可以发现有\(x\)单调递增而\(f_x\)单调递减

显然有对于相同的一个\(f_x\)我们应当去取其最前面的点,因为其\(x\)是最小的

接下来我们从后往前观察,可以发现\(f_x\)的递推式是:\(f_x=max(f_{x+1},t_x-x)\)

不妨假定\(f_n\)可能成为一个合法的答案,那么下一个可能可以成为答案的\(f_x\)的值便是恰好比当前\(f_n\)大的\(t_i-i\)

假设我们得知了这些有用的\(f_x(\)即权值不同的\(f_x)\),假定他们依次为:

\[f_n,f_{p_1},f_{p_2},f_{p_3}...f_{p_x}\]

那么答案应该就是:

\[min(f_n+p_1-1,f_{p_1}+p_2-1...f_{p_x}+1-1)+n-1\]

即:

\[min(f_n+p_1,f_{p_1}+p_2...f_{p_x}+1)+n\]

所以我们可以\(O(n)\)的扫一遍过去,然后求一下权值即可...

但是这样复杂度只能做到\(O(qn)\)

我们考虑利用线段树来维护这个序列,考虑某一个区间\(~[l,r]~\),我们可以将其答案定义为:

\[w_{l,r}=\min_{x=l}^r(x+\max_{i=x}^{r}(t_i-i))\]

令:

\[a_{l,r}=\max_{i=l}^r(t_i-i)\]

考虑如何合并区间,首先可以发现右儿子的区间信息是不会受到影响的,于是需要维护的只有左区间

我们考虑拿右端点的区间最大值去询问左区间,就完成了\(pushup\)

如何询问?

对于左区间,我们用右区间的最大值\(k\)去询问,则每个点\(x\)的答案变成了:

\[x+\max(\max_{i=x}^r(t_i-i),k)\]

如果某个区间的最大值比\(k\)要小,则答案显然为\(l+k\)

否则我们将这个区间拆开变成\([l,mid]\)以及\([mid+1,r]\)

显然有如果\(a_{mid+1,r}<k\)那么对于区间\([mid+1,r]\)我们可以直接上传,对于\([l,mid]\)我们则递归处理

如果有:\(a_{mid+1,r}>k\),那么对于区间\([mid+1,r]\)我们可以递归询问,同时,对于这个区间的左区间,会发现这个答案我们实际上以及维护过了!因为我们接下来所做的将会是那\(a_{mid+1,r}\)去询问\([l,mid]\)而这完全没必要

综上,\(pushup\)的复杂度可以做到\(O(\log n)\)

所以整体复杂度为\(O(n \log^2 n)\)

接下来是关于代码实现的一点细节:

\(1.\)实际上对于每个点我们并不需要维护出其答案,我们可以令\(tr[x].w\)表示的是使用其右儿子中的最大值去询问其左儿子区间的答案,你会惊人的发现最后我们要求的答案即对\([1,n]\)\([n+1,2n]\)询问,所以也就是\(tr[1].w\)

然后这样代码会非常好实现

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define ls(x) x * 2
#define rs(x) x * 2 + 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 N = 2e5 + 5 ;
int n, m, type, t[N], f[N] ; 
struct Tr {
    int mx, w ;
} tr[N * 4] ;
int query( int x, int l, int r, int w ) {
    if( l == r ) return l + max( w, tr[x].mx ) ;
    int mid = ( l + r ) >> 1 ; 
    if( tr[rs(x)].mx >= w ) return min( tr[x].w, query( rs(x), mid + 1, r, w ) ) ;
    else return min( mid + 1 + w, query( ls(x), l, mid, w ) ) ; 
}
void pushup( int x, int l, int r, int mid ) {
    tr[x].mx = max( tr[ls(x)].mx, tr[rs(x)].mx ) ; 
    tr[x].w = query( ls(x), l, mid, tr[rs(x)].mx ) ;
}
void update( int x, int l, int r, int wh, int k ) {
    if( l == r ) { tr[x].mx = k, tr[x].w = k + l; return ; }
    int mid = ( l + r ) >> 1 ;
    if( mid >= wh ) update( ls(x), l, mid, wh, k ) ;
    else update( rs(x), mid + 1, r, wh, k ) ;
    pushup( x, l, r, mid ) ;
}
void build( int x, int l, int r ) {
    if( l == r ) { tr[x].mx = t[l]; tr[x].w = tr[x].mx + l; return ; }
    int mid = ( l + r ) >> 1 ;
    build( ls(x), l, mid ), build( rs(x), mid + 1, r ) ; 
    pushup( x, l, r, mid ) ;
}
signed main() 
{
    n = gi(), m = gi(), type = gi() ;  
    rep( i, 1, n ) t[i] = gi() - i, t[i + n] = t[i] - n ; 
    build( 1, 1, 2 * n ) ; int lastans = tr[1].w + n - 1 ; 
    printf("%d\n", lastans ) ;
    while( m -- ) {
        int x = gi() ^ ( type * lastans ), y = gi() ^ ( type * lastans ) ;
        update( 1, 1, 2 * n, x, y - x ), update( 1, 1, 2 * n, x + n, y - x - n ) ; 
        printf("%d\n", lastans = tr[1].w + n - 1 ) ;
    } 
    return 0 ;
}

猜你喜欢

转载自www.cnblogs.com/Soulist/p/11688603.html