• 「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

    NOIP 前写下这篇题解,目前看来这部分算法实战中价值不太大,先简单写一写。

    注意到图 \(G_t\)\(G_{t+1}\) 其实有很多重复的部分和少量的区别。我们可以基于此,找出每个图上的“关键”点——在两个图上出边有区别的点。如果我们不会模拟到这些关键点上,则\(G_t\)\(G_{t+1}\) 上模拟是一致的;否则,我们又相当于确定了起点,此时单独在关键点之间倍增,问题就简单多了。

    最终可以得到 \(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() {
        freopen( "reflection.in", "r", stdin );
        freopen( "reflection.out", "w", stdout );
        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;
    }
    
  • 相关阅读:
    php 微信调用扫一扫
    JavaSE常用API
    Java中的异常处理
    Java实现多态的机制是什么?
    JavaSE(下)
    JavaSE语法(中)
    JavaSE语法
    Java面向对象
    Java零基础入门之常用工具
    Java抽象类、接口、内部类
  • 原文地址:https://www.cnblogs.com/crashed/p/15569930.html
Copyright © 2020-2023  润新知