「IOI2021」Dungeons

题目

点这里看题目。

分析

比较考察基础的观察和诡异的优化的题目,值得一试。

算法 1

直接模拟,复杂度为 \(O(qs)\)

算法 2

移动的重复过程相当多,可以想到使用倍增压缩

针对 lose 的情况写一个倍增可以得到 Subtask3 的分数。针对 win 的情况写一个倍增可以得到 Subtask2 的分数。

算法 3

单纯对于移动过程进行倍增,优化效果不明显。而注意到 \(z\) 只有当 \(< \max s\) 的时候才需要考虑限制,当 \(z\ge\max s\) 的时候图是确定的,直接倍增就好了。

容易想到的一个方向是,找到一种特殊的过程,使得经过这个过程后的 \(z'\) 保证可以达到 \(z\) 的某个倍数,从而完成对于执行复杂度的分析。我们并不能很快就关注到了一点:当 \(z\ge s_x\) 的时候,我们不会加上一个随机的数,而恰恰是 \(s_x\)。这其实就在暗示我们 \(z'\ge 2s_x\)

我们发现了一个“两倍”的关系,虽然这和我们想要的不完全一致,但是已经可以带来一定的启发——我们可以对于 \(z\) 按照 2 的次幂划分阶段。我们面前是这样的两面情况:当不存在 \(z\ge s_x\) 这样的转移的时候,图的形态可以确定,也就意味着可以预处理;反过来,如果遇到了 \(z\ge s_x\) 这样的转移,我们只需要跨一步就可以进入到下一个阶段。

具体地,我们构造一个 \(G_t\),表示 \(z\in [2^t,2^{t+1})\) 时没有遇到 \(z\ge s_x\) 转移的图的形态。因此,对于 \(s_x<2^t\),我们连接边 \(x\overset{s_x}{\rightarrow} w_x\),而对于 \(s_x\ge 2^t\),我们连接 \(x\overset{p_x}{\rightarrow } l_x\)。这样,在一个确定的 \(G_t\),我们可以进行倍增,从而加速转移。跨阶段的情况则最多出现 \(\log s\) 次。

需要注意的是,当 \(t\ge \lfloor\log_2 \max s\rfloor\) 时,图上不存在 lose 边,而 \(z\) 也不再受权值的影响,此时的倍增上界为 \(\log_2 n\) 而不是 \(\log_2 s\)

该算法的复杂度为 \(O((n+q)\log^2 s)\)。看起来不能通过,但是注意到 \(n\)\(q\) 之间可以平衡复杂度,因此我们可以适当调低倍增上界,从而令预处理时间下降,询问时间上升,这样就能卡过去。

小结:

  1. 注意观察问题的特殊性,尤其是与常见条件不同的地方,这些往往是设置的突破口。某种程度上说,解决(人出的)问题也可以去思考出题人的意图。

  2. 注意这种“模拟”问题背后的值限制,我们往往可以去寻找一个令某个特征值“翻倍”的过程,从而分析出一个 \(O(\log x)\) 的复杂度。

  3. 学习一下平衡复杂度调节倍增的方法。

算法 4

注意到图 \(G_t\)\(G_{t+1}\) 其实有很多重复的部分和少量的区别。我们可以基于此,找出每个图上的“关键”点——在两个图上出边有区别的点。如果我们不会模拟到这些关键点上,则\(G_t\)\(G_{t+1}\) 上模拟是一致的,为了加速模拟我们可以直接升级到 \(G_{t+1}\)

区别就出现在 \(2^t \le s_x< 2^{t+1}\) 的结点 \(x\) 上,因此在图 \(G_t\) 中这样的 \(x\),加上结点 \(n\) 就是我们的关键点。

考虑我们的行走过程,我们从 \(G_t\)\(u\) 出发,而前路只有两种可能:

  1. 我们走着走着就发觉自己的 level 超过 \(G_t\)并且当前还没有经过关键点,于是我们可以假装一开始就在 \(G_{t+1}\) 上面行走,从而跳过这个阶段;

  2. 我们确实走到了一个关键点,那么我们不得不去解决在 \(G_t\) 上行走的问题。不过,如果我们在关键点上不会进入 \(G_{t+1}\),而是再向前一步,就又回到了一开始的问题,也就是消除了这个关键点的影响。

    相似的问题中,我们于是会去关心下一个关键点,重复……

    所以,关键点之间的移动是一个链式过程,我们同样可以处理 \(G_t\) 上关键点之间的倍增从而加速关键点之间的模拟过程。

涉及到的其实就是每个点会不会走到关键点、会走到哪些的问题,在每层图上可以记忆化搜索。由于每个结点只会当一次关键点,因此预处理的时间被压缩到了 \(O(n\log n+n\log S)\)

最终可以得到 \(O(n\log S+q\log^2 S)\) 的算法。

代码

算法 3

void Init() {
    mx = 0;
    rep( i, 1, N ) mx = MAX( mx, S[i] );
    lg2 = log2( mx );
    // lg2 = max{ x | 2^x <= mx }
    // so 2^{lg2+1} > mx for sure
    rep( t, 0, lg2 ) {
        rep( i, 1, N ) {
            int upper = ( 1 << ( t + 1 ) ) - 1;
            if( S[i] < ( 1 << t ) ) {
                go[t][i][0] = W[i];
                gain[t][i][0] = S[i];
                lim[t][i][0] = upper - S[i];
            } else {
                go[t][i][0] = L[i];
                gain[t][i][0] = P[i];
                lim[t][i][0] = MAX( 0, MIN( S[i] - 1, upper - P[i] ) );
            }
        }
        lim[t][N + 1][0] = 0;
        gain[t][N + 1][0] = 0;
        go[t][N + 1][0] = N + 1;
        rep( j, 1, lg2 ) rep( i, 1, N + 1 ) {
            go[t][i][j] = go[t][go[t][i][j - 1]][j - 1];
            gain[t][i][j] = gain[t][i][j - 1] + gain[t][go[t][i][j - 1]][j - 1];
            lim[t][i][j] = MAX( 0ll, MIN( ( LL ) lim[t][i][j - 1], lim[t][go[t][i][j - 1]][j - 1] - gain[t][i][j - 1] ) );
        }
    }
    int t = lg2 + 1;
    rep( i, 1, N ) 
        go[t][i][0] = W[i],
        gain[t][i][0] = S[i];
    gain[t][N + 1][0] = 0;
    go[t][N + 1][0] = N + 1, fin = log2( N );
    rep( j, 1, fin ) rep( i, 1, N + 1 ) {
        go[t][i][j] = go[t][go[t][i][j - 1]][j - 1];
        gain[t][i][j] = gain[t][i][j - 1] + gain[t][go[t][i][j - 1]][j - 1];
    }
}

LL Simulate( int x, LL z ) {
    for( int t = ( int ) log2( z ) ; t <= lg2 ; t ++ ) {
        for( int i = lg2 ; ~ i ; i -- ) 
            if( z <= lim[t][x][i] )
                z += gain[t][x][i], 
                x = go[t][x][i];
        if( x == N + 1 ) break;
        if( S[x] < ( 1 << t ) || z >= S[x] )
            z += S[x], x = W[x];
        else if( z < S[x] )
            z += P[x], x = L[x];
    }
    if( x != N + 1 ) {
        int t = lg2 + 1;
        for( int i = fin ; ~ i ; i -- )
            if( go[t][x][i] != N + 1 )
                z += gain[t][x][i], x = go[t][x][i];
        z += S[x];
    }
    return z;
}

算法 4

#include <cmath>
#include <cstdio>

typedef long long LL;

const int INF = 0x3f3f3f3f;
const int MAXN = 4e5 + 5, MAXLOG = 25;

template<typename _T>
void read( _T &x ) {
    x = 0; char s = getchar(); bool f = false;
    while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
    while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
    if( f ) x = -x;
}

template<typename _T>
void write( _T x ) {
    if( x < 0 ) putchar( '-' ), x = -x;
    if( 9 < x ) write( x / 10 );
    putchar( x % 10 + '0' );
}

template<typename _T>
_T MIN( const _T a, const _T b ) {
    return a < b ? a : b;
}

template<typename _T>
_T MAX( const _T a, const _T b ) {
    return a > b ? a : b;
}

LL gain[MAXN][MAXLOG];
int go[MAXN][MAXLOG];

LL jmp[MAXN][MAXLOG];
int nxt[MAXN][MAXLOG], lim[MAXN][MAXLOG];

LL wei[MAXN][MAXLOG];
int bound[MAXN][MAXLOG], tar[MAXN][MAXLOG];
bool vis[MAXN];

int S[MAXN], P[MAXN], W[MAXN], L[MAXN];

int up, dn;
int N, Q, lg2, fin;

void DFS( const int u, const int t ) {
    if( ~ tar[u][t] || vis[u] ) return ;
    vis[u] = true;
    if( S[u] < ( 1 << t ) ) {
        DFS( W[u], t );
        tar[u][t] = tar[W[u]][t];
        wei[u][t] = wei[W[u]][t] + S[u];
        bound[u][t] = MAX( 0, MIN( up - 1, bound[W[u]][t] ) - S[u] );
    } else {
        DFS( L[u], t ); 
        tar[u][t] = tar[L[u]][t]; 
        wei[u][t] = wei[L[u]][t] + P[u];
        bound[u][t] = MAX( 0, MIN( S[u] - 1, MIN( up - 1, bound[L[u]][t] ) - P[u] ) );
    }
}

void Init() {
    int mx = 0;
    for( int i = 0 ; i < N ; i ++ )
        mx = MAX( mx, S[i] );
    lg2 = log2( mx ), fin = log2( N );
    for( int t = 0 ; t <= lg2 ; t ++ ) {
        up = ( 1 << ( t + 1 ) ), dn = 1 << t;
        for( int i = 0 ; i <= N ; i ++ )
            tar[i][t] = -1, bound[i][t] = 0, vis[i] = false;
        for( int i = 0 ; i <= N ; i ++ )
            if( i == N || ( dn <= S[i] && S[i] < up ) )
                tar[i][t] = i, wei[i][t] = 0, bound[i][t] = INF;
        for( int i = 0 ; i <= N ; i ++ ) DFS( i, t );
        for( int i = 0 ; i < N ; i ++ )
            if( dn <= S[i] && S[i] < up ) {
                if( ~ tar[L[i]][t] ) {
                    nxt[i][0] = tar[L[i]][t]; 
                    jmp[i][0] = P[i] + wei[L[i]][t];
                    lim[i][0] = MAX( 0, MIN( MIN( up - 1 - P[i], S[i] - 1 ), 
                                             bound[L[i]][t] - P[i] ) );
                } else
                    nxt[i][0] = -1, jmp[i][0] = 0, lim[i][0] = 0;
            }
    }
    nxt[N][0] = N, lim[N][0] = 0;
    for( int j = 1 ; j <= lg2 ; j ++ )
        for( int i = 0 ; i <= N ; i ++ ) {
            int p = nxt[i][j - 1];
            if( p == -1 || nxt[p][j - 1] == -1 ) {
                nxt[i][j] = -1;
                jmp[i][j] = lim[i][j] = 0;
                continue;
            }
            nxt[i][j] = nxt[p][j - 1];
            jmp[i][j] = jmp[i][j - 1] + jmp[p][j - 1];
            lim[i][j] = MAX( 0ll, MIN( ( LL ) lim[i][j - 1], lim[p][j - 1] - jmp[i][j - 1] ) );
        }

    gain[N][0] = 0, go[N][0] = N;
    for( int i = 0 ; i < N ; i ++ )
        gain[i][0] = S[i], go[i][0] = W[i];
    for( int j = 1 ; j <= fin ; j ++ )
        for( int i = 0 ; i <= N ; i ++ )
            go[i][j] = go[go[i][j - 1]][j - 1],
            gain[i][j] = gain[i][j - 1] + gain[go[i][j - 1]][j - 1];
}

LL Query( int x, LL z ) {
    for( int t = 0 ; t <= lg2 ; t ++ ) {
        if( z >= ( 1 << ( t + 1 ) ) ) continue;
        if( z > bound[x][t] || tar[x][t] == -1 ) continue;
        z += wei[x][t], x = tar[x][t];
        for( int i = lg2 ; ~ i ; i -- )
            if( ~ nxt[x][i] && z <= lim[x][i] ) 
                z += jmp[x][i], x = nxt[x][i];
        if( x == N ) break;
        if( z < S[x] ) z += P[x], x = L[x];
        else z += S[x], x = W[x];
    }
    if( x != N ) {
        for( int i = fin ; ~ i ; i -- )
            if( go[x][i] != N )
                z += gain[x][i], x = go[x][i];
        z += S[x];
    }
    return z;
}

int main() {
    read( N ), read( Q );
    for( int i = 0 ; i < N ; i ++ ) read( S[i] );
    for( int i = 0 ; i < N ; i ++ ) read( P[i] );
    for( int i = 0 ; i < N ; i ++ ) read( W[i] );
    for( int i = 0 ; i < N ; i ++ ) read( L[i] );
    Init();
    while( Q -- ) {
        int a, b;
        read( a ), read( b );
        write( Query( a, b ) ), putchar( '\n' );
    }
    return 0;
}
posted @ 2021-11-17 22:36  crashed  阅读(205)  评论(0编辑  收藏  举报