「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;
}
原文地址:https://www.cnblogs.com/crashed/p/15552128.html