zoukankan      html  css  js  c++  java
  • 「CF1442D」Sum

    题目

    点这里看题目。

    分析

    很容易想到一个 \(O(nk^2)\) 的暴力 DP,当然也很容易看出这个 DP 根本没有和“单调不降”扯上任何关系。因此,接下来我们要做的就是利用好“单调不降”的性质。

    一个想法是凸性——数组前缀和是下凸的。不过,由于我们对下凸壳做的是 \(\max\) 卷积,所以这样的凸性暂时还没有什么用。

    另一个想法是化归方案,找出方案的某些性质。由于“单调不降”,选越多越“划算”,我们有这样的直觉——可能很多数组都是被完全选中了的,只有少数数组仅有一个前缀被选中

    接下来,我们需要抽象地研究一下。不妨假设有两个数组 \(i,j\) 都仅有一个非空真前缀被选中,一个被选了 \(l_i\) 个,另一个被选了 \(l_j\) 个。根据题目条件,有 \(a_{i,l_i}\le a_{i,l_{i}+1}\)\(a_{j,l_j}\le a_{j,l_j+1}\)。在这样的背景之下,我们还可以假设 \(a_{i,l_i}\le a_{j,l_j+1}\)。由于题目单调性的保证,我们还可以知道 \(a_{i,l_i-1}\le a_{j,l_j+2},a_{i,l_i-2}\le a_{j,l_j+3}\dots\)

    现在,我们就可以将 \(a_i\) 中被选的元素调整\(a_j\) 中去,得到一个不劣的方案,直到 \(a_i\) 中一个都没有被选或 \(a_j\) 被完全选中。因此,我们可以知道,一定有一种最优解,满足仅有至多一个数组,它仅有一个非空真前缀被选中

    我们现在可以枚举特殊的一个数组,剩下的就是 01 背包问题。如果暴力做 01 背包仍然会不幸地得到 \(O(nk^2)\) 的算法。一个在多点运算中常用的技巧是,进行自底向上或自顶向下的分治。最终我们可以得到一个 \(O(nk\log n)\) 的算法。

    小结:

    1. 注意对问题的感知
    2. 灵活运用调整的思想,分析有效方案的特点,缩减需要搜索的范围;
    3. 注意一下分治方法的应用。

    代码

    #include <cstdio>
    #include <vector>
    
    #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 LL INF = 1e18;
    const int MAXN = 3005;
    
    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 MAX( const _T a, const _T b ) {
        return a > b ? a : b;
    }
    
    std :: vector<LL> q[MAXN];
    
    LL dp[MAXN], buc[20][MAXN];
    
    LL su[MAXN];
    int siz[MAXN];
    
    LL ans;
    int N, K;
    
    #define ID( l, r ) ( ( (l) + (r) ) | ( (l) != (r) ) )
    
    void Divide( const int l, const int r, const int dep ) {
        if( l == r ) {
            for( int i = 0 ; i <= siz[l] && i <= K ; i ++ )
                ans = MAX( ans, q[l][i] + dp[K - i] );
            return ;
        }
        int mid = ( l + r ) >> 1;
        rep( j, 0, K ) buc[dep][j] = dp[j];
        rep( j, mid + 1, r )
            per( k, K, siz[j] )
                dp[k] = MAX( dp[k], dp[k - siz[j]] + su[j] );
        Divide( l, mid, dep + 1 );
        rep( j, 0, K ) dp[j] = buc[dep][j];
        rep( j, l, mid )
            per( k, K, siz[j] )
                dp[k] = MAX( dp[k], dp[k - siz[j]] + su[j] );
        Divide( mid + 1, r, dep + 1 );
        rep( j, 0, K ) dp[j] = buc[dep][j];
    }
    
    int main() {
        read( N ), read( K );
        rep( i, 1, N ) {
            LL pref = 0;
            read( siz[i] );
            q[i].push_back( 0 );
            rep( j, 1, siz[i] ) {
                int x; read( x );
                q[i].push_back( pref += x );
            }
            su[i] = pref;
        }
        ans = - INF;
        rep( j, 0, K ) dp[j] = - INF;
        dp[0] = 0;
        Divide( 1, N, 0 );
        write( ans ), putchar( '\n' );
        return 0;
    }
    
  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/crashed/p/15552128.html
Copyright © 2011-2022 走看看