Solution -「多校聯訓」假人
\(\mathcal{Description}\)
Link.
一種物品有 長度 和 權值 兩種屬性,現給定 \(n\) 組物品,第 \(i\) 組有 \(k_i\) 個,分別為 \((1,a_{i,1})..(k_i,a_{i,k_i})\),求在每組物品裡恰好選擇一個物品,且物品長度和恰為 \(i=n..\sum k\) 時的最大物品權值和。
\(n\le10^5\),\(k_i\le5\)。
\(\mathcal{Solution}\)
本次 NOIP 模擬賽 考察的知識點包括但不限於:凸性函式的研究應用、閔可夫斯基和、Gale-Ryser 定理、LCT……是一套非常優秀的組題。
先令所有長度 \(-1\),長度區間變為 \([0,K=4]\)。考慮暴力 DP,令\(f(i,j)\) 在前 \(i\) 組中選出長度和為 \(j\) 的物品時最大權值和。
結論:令 \(L=12\),\(f_{i,r}(x)=f(i,xL+r)~(r\in[0,L),x\in\mathbb N)\),則 \(f_{i,r}(x)\) 的影象是上凸的點集。
證明
承認這樣一個結論:對於整數集 $A$,$\forall a\in A,a\in[0,4],\sum_{a\in A}=24$,則 $\exist B\subseteq A,\sum_{b\in B}b=12$。作者水平有限,沒有找到除暴搜和冗長分類討論之外的簡潔證明。且滿足這一性質的常數值除 \(([0,4],24)\)
以外,還有許多分佈規律不明顯的解。有興趣的讀者歡迎一起討論。
此後,考慮任意 \(f_{i,r}(x-1),f_{i,r}(x),f_{i,r}(x+1)\)。研究從 \(f_{i,r}(x-1)\) 的最優解調整得到 \(f_{i,r}(x+1)\) 最優解的過程,設第 \(j\) 組物品所選長度變化量為 \(\Delta_j\),可以看出 \(\Delta_j\in[-4,4]\),且 \(\sum\Delta_j=2L=24\)。我們能通過貪心的方式構造將 \(\Delta_j\) 分組,使得每組 \(\Delta_j\) 之和 \(\in[0,4]\),運用上文結論,得到兩組長度變化量為 \(12\)
利用結論,分治 + 閔可夫斯基和求出函式 \(f_{n,0..L-1}\) 即可。複雜度 \(\mathcal O(LKn\log n)\)。
\(\mathcal{Code}\)
由於著急補題,直接套了計算幾何的板子。針對凸性 DP 的高效歸併可以看 OneInDark 的部落格。
/*~Rainybunny~*/
#ifndef RYBY
#pragma GCC optimize( "Ofast" )
#endif
#include <bits/stdc++.h>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
typedef long long LL;
inline char fgc() {
static char buf[1 << 17], *p = buf, *q = buf;
return p == q && ( q = buf + fread( p = buf, 1, 1 << 17, stdin ), p == q )
? EOF : *p++;
}
inline int rint() {
int x = 0, s = fgc();
for ( ; s < '0' || '9' < s; s = fgc() );
for ( ; '0' <= s && s <= '9'; s = fgc() ) x = x * 10 + ( s ^ '0' );
return x;
}
inline void wint( const LL x ) {
if ( 9 < x ) wint( x / 10 );
putchar( x % 10 ^ '0' );
}
inline void chkmax( LL& u, const LL v ) { u < v && ( u = v ); }
const int MAXN = 1e5;
namespace PGP {
const double EPS = 1e-9, PI = acos( -1. );
inline int dcmp( const double a ) {
return -EPS < a && a < EPS ? 0 : a < 0 ? -1 : 1;
}
struct Point {
int x; LL y;
Point(): x( 0 ), y( 0 ) {}
Point( const int a, const LL b ): x( a ), y( b ) {}
inline Point operator + ( const Point& p ) const {
return { x + p.x, y + p.y };
}
inline Point operator - ( const Point& p ) const {
return { x - p.x, y - p.y };
}
inline LL operator ^ ( const Point& p ) const {
return x * p.y - y * p.x;
}
inline double angle() const {
double t = atan2( y, x );
return t < 0 ? t + 2 * PI : t;
}
};
typedef Point Vector;
typedef std::vector<Point> Convex;
inline Convex minkowskiSum( const Point& ap, const Point& bp,
const Convex& A, const Convex& B ) {
int n = int( A.size() ), m = int( B.size() );
static Convex ret; ret.clear(), ret.resize( n + m + 1 );
ret[0] = ap + bp;
int i = 0, j = 0, k = 0;
while ( i < n && j < m ) {
ret[k + 1] = ( ret[k] + ( ( A[i] ^ B[j] ) > 0 ? A[i++] : B[j++] ) );
++k;
}
while ( i < n ) ret[k + 1] = ret[k] + A[i++], ++k;
while ( j < m ) ret[k + 1] = ret[k] + B[j++], ++k;
return ret;
}
} using namespace PGP;
struct Atom {
std::vector<Point> f;
friend inline Atom operator + ( const Atom& u, const Atom& v ) {
static std::vector<Point> ur[12], vr[12]; static Atom ret;
static Point up[12], vp[12];
ret.f.clear(), ret.f.resize( u.f.size() + v.f.size() - 1 );
rep ( i, 0, int( ret.f.size() ) - 1 ) ret.f[i].x = i;
rep ( i, 0, 11 ) ur[i].clear(), vr[i].clear();
rep ( i, 0, int( u.f.size() ) - 1 ) ur[i % 12].push_back( u.f[i] );
rep ( i, 0, int( v.f.size() ) - 1 ) vr[i % 12].push_back( v.f[i] );
rep ( i, 0, 11 ) {
std::reverse( ur[i].begin(), ur[i].end() );
std::reverse( vr[i].begin(), vr[i].end() );
int n = int( ur[i].size() );
if ( n ) {
up[i] = ur[i][0];
rep ( j, 0, n - 2 ) ur[i][j] = ur[i][j + 1] - ur[i][j];
ur[i][n - 1] = up[i] - ur[i][n - 1];
}
n = int( vr[i].size() );
if ( n ) {
vp[i] = vr[i][0];
rep ( j, 0, n - 2 ) vr[i][j] = vr[i][j + 1] - vr[i][j];
vr[i][n - 1] = vp[i] - vr[i][n - 1];
}
}
rep ( i, 0, 11 ) if ( !ur[i].empty() ) {
rep ( j, 0, 11 ) if ( !vr[j].empty() ) {
const auto&& cvx( minkowskiSum( up[i], vp[j], ur[i], vr[j] ) );
for ( size_t k = 0; k < cvx.size(); ++k ) {
chkmax( ret.f[cvx[k].x].y, cvx[k].y );
}
}
}
return ret;
}
};
inline Atom solve( const int l, const int r ) {
if ( l == r ) {
int k = rint(), v;
static Atom ret; ret.f.resize( k );
rep ( i, 1, k ) ret.f[i - 1] = { i - 1, rint() };
return ret;
}
int mid = l + r >> 1;
const Atom &&u( solve( l, mid ) ), &&v( solve( mid + 1, r ) );
return u + v;
}
int main() {
freopen( "fake.in", "r", stdin );
freopen( "fake.out", "w", stdout );
const Atom&& ans( solve( 1, rint() ) );
for ( const auto& u: ans.f ) wint( u.y ), putchar( ' ' );
putchar( '\n' );
return 0;
}