题目
点这里看题目。
分析
很容易想到一个 \(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)\) 的算法。
小结:
- 注意对问题的感知;
- 灵活运用调整的思想,分析有效方案的特点,缩减需要搜索的范围;
- 注意一下分治方法的应用。
代码
#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;
}