• 「NOI2022」移除石子


    题目

    对于一个长度为 \(n\) 的正整数序列 \(\{a\}\) 和参数 \(K\),你可以进行如下三种操作:

    1. 选择一个 \(1\le i\le n\),满足 \(a_i\ge 2\),并选取一个 \(2\le k\le a_i\),令 \(a_i\gets a_i-k\)

    2. 选择一个连续的编号区间 \([l,r]\) 满足 \(1\le l,l+2\le r\le n,\min_{l\le i\le r}a_i>0\),令 \(l\le i\le r\) 的每一个 \(a_i\gets a_i-1\)

    3. 选择一个 \(1\le i\le n\),令 \(a_i\gets a_i+1\)

    第三种操作必须在前两种操作之前进行,且必须恰好进行 \(K\)。而进行完第三种操作之后,你可以按任意顺序执行任意多次前两种操作。如果存在一种操作的方式,使得 \(\{a\}\) 可以变成一个全 \(0\) 的序列的话,我们就说 \(\{a\}\) 是“好”的序列。

    现在,给定 \(n\)\(K\),以及 \(n\) 对参数 \(\{(L_i,R_i)\}\)。如果我们允许 \(a_i\)\([L_i,R_i]\) 中任意整数,则所有的可能的 \(\{a\}\) 中,有多少个是“好”的?答案对 \(10^9+7\) 取模。

    对于 \(100\%\) 的数据,满足 \(3\le n\le 1000,0\le K\le 100, 0\le L_i\le R_i\le 10^9\)

    多组数据,单个测试点数据组数不超过 \(10\) 组。

    分析

    GG 了,一上来连 DP 套 DP 都没想到......

    25 pts

    你这出题人给分也太吝啬了。

    首先考虑,怎么进行 \(L_i=R_i\) 的检查?我最开始的想法是针对序列本身进行讨论,这可以在 \(R_i\le 2\) 的时候生效,但深入下去就很恼火了。

    想一想为什么?直接讨论的困难在于,可能的操作方案实在是太多了。凭借直觉构造无异于盲目搜索,更何况我向来不擅长这种东西。因此,我们首先需要做的应当是将操作归约到某些特殊情况上

    从这个角度入手,我们不难想到如下几个事实:

    Conclusion.

    1. 任意操作二的编号区间长度 \(\le 5\)

    2. 不存在两次操作二操作的编号区间完全一样。


    Proof.

    1. \(\ge 6\) 的,对半剖开一定可以变成两个不短于 \(3\) 的。

    2. 如果出现了两次以上对于 \([l,r]\) 的操作,我们可以转而对于 \(l\le i\le r\) 的每一个 \(a_i\) 执行相同数量的操作一。

    Note.

    这种复杂操作的问题,首先研究操作的特性、归约的方法应该是基础的思路。尤其需要关注多种操作互相配合产生的效果

    我们的目标就应该是,尽量把涉及到的操作变短、变少、变简洁

    现在,从每个位置起始的操作二只有至多三次(对应长度为 \(3,4,5\) 的操作区间),而操作一非常灵活。因此,我们可以想到确定操作二的情况进行检查,而实际上从前往后扫描时,我们只需要知道较近的若干个操作二的情况即可。

    因此,可以设计 DP:\(f_{i,a,b,c,d}\) 表示到第 \(i\) 个位置时,\(i-4\sim i-1\) 的操作二区间个数分别为 \(a,b,c,d\) 时,前缀 \(i\) 能否被清除完。进一步地,由于转移过程只需要考虑多少个操作二区间被 terminate 了,\(a,b,c\) 实际上是本质相同的,状态可以被改写成 \(g_{i,e,d},e=a+b+c\)

    很明显我们需要关注 \(e,d\) 的范围。现在是 \(0\le e\le 9,0\le d\le 3\)。更深入的分析表明范围可以缩小,但在这个部分里已经够用了。

    40 pts

    你这出题人给分也太吝啬了。

    此时需要考虑 \(K\) 的影响。直觉是,\(K\) 和“好不好”应该存在单调性,但是(原)题面似乎否定了这一点。那么,我们就来看一下单调性的问题。

    单调性其实就是看“好的序列再执行一次操作三是否仍然好”,指向的是操作的调整。从这一点入手,我们可以得出:

    Conclusion.

    当且仅当 \(n\) 和“好”的序列 \(\{a\}\) 满足下列条件之一时,对于 \(\{a\}\) 多进行一次操作三总会导致 \(\{a\}\) 变得不“好”:

    1. \(n=3,\{a\}=\{1,1,1\}\)

    2. \(\{a\}=\{0\}_{i=1}^n\)


    Proof.

    正向的分析。

    首先,如果某种操作方案中存在操作一,我们可以直接在这个操作一的位置上进行操作三,然后相应地增加操作一的 \(k\)

    其次,如果某种操作方案中存在操作二,且对应的 \([l,r]\neq [1,n]\),我们可以在 \(l-1\) 或者 \(r+1\) 处进行操作三,然后扩展操作二的区间。

    现在,可能变不“好”的序列只有全 \(1\) 的序列和全 \(0\) 的序列。在 \(n>3\) 时,对于全 \(1\) 的序列而言,我们可以对于 \(a_1\) 进行操作三,然后添加一次针对 \(a_1\) 的操作一,最后进行一次 \([2,n]\) 的操作二。

    最后就可以得到上面的结论。

    Note.

    具体操作的核心思路是:对于已有的方案执行微调。在已有的方案上面施工是便利的,轻易抛弃它常常是愚蠢的。

    也就是说,我们可以毫不费力地将 \(g\) 改成对于“最小进行操作三次数”的 DP。最后只需要额外判断一下两种不合单调性的情况即可。

    55 pts

    现在进入了计数问题,我们也自然地想到了 DP 套 DP 的思路。

    问题是,即便是布尔 DP,\(g_i\) 也有足足 \(40\) 个值,我们需要压缩。

    第一步压缩比较简单。如果以某个位置出发的操作二区间真的有 \(3\) 个,我们其实可以通过操作二,将长度为 \(4,5\) 的切掉一部分:

    1 ---       1 ---
    2 ----   => 2 |---
    3 -----     3 |----
    
    横线表示操作二区间,竖线表示操作一区间。
    

    那么,\(g_{i,e,d}\)\(e,d\) 可以被压缩到 \(0\le e\le 6,0\le d\le 2\),总共 \(18\) 个值。如果常数充分小,跑一跑 \(2^18\) 的 DP 说不定可以过(。

    再压缩就很困难了。\(d\) 似乎已经走到了尽头,但是 \(e\) 真的需要这么大吗?实际上,

    Note.

    个人认为,这里的难点在于“想到并相信 \(e\) 的上界可以继续收缩”。这有点反直觉,但是我们必须看明白这样一个事实,就是 \(e\) 是一个合并过的变量,\(d\) 是单个的变量。\(d\le 2\) 可能是紧的,但是 \(e\le 6\) 可能还是松的,也必须意识到多个操作组合起来威力巨大。

    想到这一点之后,后续的检查其实相对简单。为什么呢?因为有了参考答案,可以对拍验证

    最终,我们可以压缩到 \(0\le e\le 2,0\le d\le 2\)。此时跑 DP 套 DP 就不难了。

    注意在这个范围下,\(a\) 实际上只需要枚举到 \(6\)\(\ge 6\)\(a\) 没有本质区别。

    100 pts

    是的,这个部分分分布实在是太糟糕了。

    此时内层的 \(g\) 是对于“最小值”的 DP。从暴力的角度来考虑,我们仍然可以执行先前的 DP 套 DP,只不过现在的内层状态达到了 \((K+2)^9\) 之巨。

    现在你会想什么?放弃这个做法,还是 tricky 地使用 std :: map 来存放状态?

    肯定选择后者啊!明显可以和之前的东西兼容嘛,而且容易从 55pts 的代码改过来,写起来肯定容易。写对了就好了,过不过得了根本不归我管。

    相当有效。实际上,可能遇到的状态只有 \(8000\sim 9000\) 个。这个做法可以进一步优化——我们提前将所有状态搜出来,顺便把转移表也打出来,这就相当于建立了一个 DFA,之后对着转移即可。

    Note.

    DP 内层实际上就是装了一个 DFA。而这告诉我们,看起来状态数很多的 DFA,实际上能用的状态可能根本没多少

    因此,如果再遇到一个很大的 DFA,我们应当尝试搜索一下它的有效状态再评估思路的可行性。

    代码

     #include <map>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    typedef long long LL;
    
    const int mod = 1e9 + 7;
    const int MAXN = 1e3 + 5, MAXS = 9e3 + 5;
    
    template<typename _T>
    inline 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>
    inline void Write( _T x ) {
        if( x < 0 ) putchar( '-' ), x = -x;
        if( 9 < x ) Write( x / 10 );
        putchar( x % 10 + '0' );
    }
    
    template<typename _T>
    inline _T Min( const _T &a, const _T &b ) {
        return a < b ? a : b;
    }
    
    template<typename _T>
    inline _T Max( const _T &a, const _T &b ) {
        return a > b ? a : b;
    }
    
    std :: map<LL, int> mp;
    int tsf[MAXS][9], ntot = 0;
    bool imp[MAXS];
    
    int f[2][MAXS];
    int dp[2][3][3];
    
    int L[MAXN], R[MAXN];
    
    int N, K;
    
    inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
    inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
    inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
    
    inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
    inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
    inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
    
    inline bool TestAllZero() {
        rep( i, 1, N )
            if( L[i] > 0 )
                return false;
        return K == 1;
    }
    
    inline bool TestAllOne() {
        rep( i, 1, N )
            if( R[i] < 1 || 1 < L[i] )
                return false;
        return N == 3 && K == 1;
    }
    
    int DFS( const LL hsh ) {
        if( mp.find( hsh ) != mp.end() ) return mp[hsh];
        int cur = ++ ntot; 
        imp[mp[hsh] = cur] = ( hsh % ( K + 2 ) ) <= K;
        rep( v, 0, 8 ) {
            LL tmp = hsh;
            rep( i, 0, 2 ) rep( j, 0, 2 ) {
                dp[0][i][j] = tmp % ( K + 2 ), tmp /= K + 2; 
                dp[1][i][j] = K + 1;
            }
            rep( a, 0, 2 ) rep( b, 0, 2 ) {
                if( dp[0][a][b] > K ) continue;
                rep( p, 0, a ) rep( r, 0, 2 ) if( a - p + b <= 2 ) {
                    int t = v - a - b - r;
                    dp[1][a - p + b][r] = Min( dp[1][a - p + b][r], dp[0][a][b] + Max( - t, 0 ) + ( t == 1 ) );
                }
            }
            tmp = 0;
            per( a, 2, 0 ) per( b, 2, 0 )
                tmp = tmp * ( K + 2 ) + dp[1][a][b];
            tsf[cur][v] = DFS( tmp );
        }
        return cur;
    }
    
    int main() {
        int T; Read( T );
        while( T -- ) {
            Read( N ), Read( K );
            rep( i, 1, N ) Read( L[i] ), Read( R[i] );
            ntot = 0, mp.clear(); LL beg = 0;
            per( i, 8, 1 ) beg = beg * ( K + 2 ) + K + 1;
            DFS( beg * ( K + 2 ) );
            rep( i, 1, ntot ) f[0][i] = f[1][i] = 0;
            int pre = 1, nxt = 0; AddEq( f[nxt][1], 1 );
            rep( i, 1, N ) {
                pre ^= 1, nxt ^= 1;
                rep( j, 1, ntot ) if( f[pre][j] ) {
                    for( int k = L[i] ; k <= R[i] && k < 8 ; k ++ )
                        AddEq( f[nxt][tsf[j][k]], f[pre][j] );
                    if( R[i] >= 8 ) AddEq( f[nxt][tsf[j][8]], Mul( f[pre][j], ( R[i] - Max( L[i], 8 ) + 1 ) % mod ) );
                    f[pre][j] = 0;
                }
            }
            int ans = 0;
            rep( j, 1, ntot ) if( imp[j] && f[nxt][j] )
                AddEq( ans, f[nxt][j] );
            if( TestAllZero() ) SubEq( ans, 1 );
            if( TestAllOne() ) SubEq( ans, 1 );
            Write( ans ), putchar( '\n' );
        }
        return 0;
    }
    
  • 相关阅读:
    shell的一本书
    linux设置网络三种方法
    BIOS讲解
    对于ssh和hadoop联系讲解和ssh的基本内容
    Httphandler
    ASP.NET配置文件
    Httpmoudle
    ASP.NET页面生命周期
    ASP.NET页面跳转方法的集合
    OutputCache的使用
  • 原文地址:https://www.cnblogs.com/crashed/p/16652170.html
Copyright © 2020-2023  润新知