「模擬賽20211004」A Fake Man
題目
給定 \(n\) 組話,第 \(i\) 組話包含 \(k_i\) 句話,其中按照長度從小到大排序後的第 \(j\) 句話長度為 \(j\),貢獻為 \(a_{i,j}\)。
現在可以從每組話中選且僅選一句話。求當方案中話的長度之和分別為 \(t,t\in[n,\sum k]\) 時最大的貢獻之和。
對於 \(100\%\) 的資料,滿足 \(1\le n\le 10^5,1\le k_i\le 5,0\le a_{i,j}\le 10^9\)。
分析
容易觀察出一個 DP 來:設 \(f_{i,j}\) 為考慮前 \(i\) 組話後,總長度為 \(j\) 的最大貢獻之和:
\[f_{i,j}=\max_{1\le p\le \min\{k_i,j\}}\{f_{i-1,j-p}+a_{i,p}\} \]這個轉移做的就是 \(\max\)
但是顯然這個 \(a\) 根本不包含任何凸性,所以我們得再思考一下。
首先,將所有話的長度減一,這樣所有話的長度一定落在 \([0,4]\) 裡面。
有趣的是,這樣的數構成的可重集,即包含的數均落在 \([0,4]\) 內部的可重集,如果它們的和為 24,那麼一定存在某種分拆方案,給出兩個可重集,和均為 12。這個可以使用搜索來驗證。
那麼考慮 \(f_{i,j}\) 到 \(f_{i,j+24}\)(如果兩者均有意義),對於這和為 24 的可重集,我們可以將它們分拆為兩個和為 12 的可重集。貪心地想,我們肯定會將貢獻較大的一個可重集優先給 \(f_{i,j+12}-f_{i,j}\)
注意到,這裡 \(j,j+12,j+24\) 一定對 \(K=12\) 同餘。因此,我們實際上發現了,對 \(K\) 同餘的 DP 值一定是凸的。
那麼這就好辦了。雖然兩個 DP 數組合並的時候,我們不能直接套 Minkowski 和,但是我們可以將每組凸殼都拿出來兩兩合併。這樣我們需要進行 \(K^2\) 次合併,每次複雜度為 \(O(\frac{n}{K})\),單次合併的複雜度即為 \(O(nK)\)
小結:
- 雖然圖形本身不一定凸,但是我們可以將圖形拆分成若干凸殼來兩兩合併,這個方法其實是通用的;
- 關注一下這裡提到的可重集拆分的結論(是不是還有類似的?);當然,這種推導凸性的方法也可以關注一下。
程式碼
#include <cstdio>
#include <vector>
#include <iostream>
#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 MAXN = 1e5 + 5;
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' );
}/*}}}*/
struct Poly/*{{{*/
{
std :: vector<LL> vec;
Poly(): vec() {}
Poly( int N ): vec( N ) {}
inline unsigned size() const { return vec.size(); }
LL operator [] ( const int idx ) const { return vec[idx]; }
inline void push_back( const LL v ) { vec.push_back( v ); }
friend Poly operator * ( const Poly &a, const Poly &b );
};/*}}}*/
int A[MAXN][10], len[MAXN];
int N;
inline void Upt( LL &x, const LL v ) { x = std :: max( x, v ); }
Poly operator * ( const Poly &a, const Poly &b )/*{{{*/
{
static LL tmp[MAXN << 2], A[MAXN << 2], B[MAXN << 2];
Poly ret( a.size() + b.size() - 1 );
int n = a.size(), m = b.size(), p, q, k = 0;
rep( r1, 0, 11 ) rep( r2, 0, 11 )
{
p = q = k = 0;
for( int i = r1 ; i < n ; i += 12 ) A[p ++] = a[i];
for( int i = r2 ; i < m ; i += 12 ) B[q ++] = b[i];
if( p == 0 || q == 0 ) continue;
per( i, p - 1, 1 ) A[i] -= A[i - 1];
per( i, q - 1, 1 ) B[i] -= B[i - 1];
for( int i = 1, j = 1 ; i < p || j < q ; )
if( i < p && ( j >= q || A[i] >= B[j] ) )
tmp[++ k] = A[i ++];
else tmp[++ k] = B[j ++];
tmp[0] = A[0] + B[0];
rep( i, 1, k ) tmp[i] += tmp[i - 1];
rep( i, 0, k ) Upt( ret.vec[i * 12 + r1 + r2], tmp[i] );
}
return ret;
}/*}}}*/
Poly Merge( const int l, const int r )/*{{{*/
{
if( l == r )
{
Poly ret( 0 );
rep( i, 0, len[l] - 1 )
ret.push_back( A[l][i] );
return ret;
}
int mid = ( l + r ) >> 1;
return Merge( l, mid ) *
Merge( mid + 1, r );
}/*}}}*/
int main()
{
read( N );
rep( i, 1, N )
{
read( len[i] );
rep( j, 0, len[i] - 1 ) read( A[i][j] );
}
Poly ans( Merge( 1, N ) );
rep( i, 0, ( int ) ans.size() - 1 )
write( ans[i] ), putchar( i == ( int ) ans.size() - 1 ? '\n' : ' ' );
return 0;
}