珠宝
loj6039「雅礼集训 2017 Day5」珠宝
题目描述
题目链接
Miranda 准备去市里最有名的珠宝展览会,展览会有可以购买珠宝,但可惜的是只能现金支付,Miranda 十分纠结究竟要带多少的现金,假如现金带多了,就会比较危险,假如带少了,看到想买的右买不到。展览中总共有 \(n\) 种珠宝,每种珠宝都只有一个,对于第 \(i\) 种珠宝,它的售价为 \(c_i\) 万元,对 Miranda 的吸引力为 \(v_i\)。Miranda 总共可以从银行中取出 k 万元,现在她想知道,假如她最终带了 \(i\) 万元去展览会,她能买到的珠宝对她的吸引力最大可以是多少?
数据范围
\(1\le n\le 1000000,1\le k\le 50000,1\le c_i\le 300,0\le v_i \le 10^9\)。
题外话
这题是真恶心啊,真给我恶心到了。
解法
首先,注意到 \(c_i\) 的范围,只有 \(300\),而物品有 \(10^5\) 个,所以我们可以先把所有价格相同的物品放到一起,然后先取价值 \(v_i\) 较大的。
可以用分组背包,然后对每个相同 \(c_i\) 的物品由价值从大到小排好序后做一个前缀和,设这个前缀和数组为 \(s_{i,j}\),表示单个花费为 \(i\) 的前 \(j\) 个物品总共所需代价。
这里预处理部分复杂度为 \(O(n)\),我代码中的为了后面好写,复杂度上界为 \(O(cn)\)。
自然而然,列出转移方程式。设 \(f_{i,j}\) 表示单个物品价格不超过 \(i\),当前可使用费用为 j 所能获得的最大价值。
可得:\(f_{i,j}=\max{\{f_{i-1,j-k\times i}+s_{i,k}\}}\)。
观察这个方程,复杂度上界为 \(O(ckn)\)。这个复杂度显然不行(不过要是硬卡应该也是有可能能卡过去的)。
考虑优化。观察上面方程式的特点,右边那个 \(s_{i,k}\) 在每个 \(i\) 之间互不影响,并且随着 \(k\) 的增大它的增长率不断减小。
考虑如何利用这个特点,首先转换一下右边 \(f\) 的第二维下标,把它写成 \(k\) ,那么后边就变成了 \(s_{i,\frac{j-k}{i}}\) 。
原式就是:\(f_{i,j}=\max{\{f_{i-1,k}+s_{i,\frac{j-k\ }{\ i}}\}}\)。
我们发现其中 \(j\) 和 \(k\) 必须在模 \(i\) 相同的情况下时,原式才有意义,所以我们考虑枚举每个在模 \(i\) 后的剩余系。
关于这个东西,打个表就会发现,它具有决策单调性。这里的决策单调性是指在从 \(k\) 转移到 \(j\) 时具有单调性。
感性理解一下第二个式子的决策单调性。其实就是指在当前每个物品的价格为 \(i\) 的情况下,只有 模 \(i\) 相同 的两个状态,才可能会有 转移关系。如果在模 \(j\) 不同的情况下直接写了决策单调性,其实就是指第一个式子中的 \(j\) 具有单调性,这是错误的。
之后,这里的单调性也不是指每个模 \(i\) 相同的数之间,他们选择当前价值为 \(i\) 的物品 选了几个 具有单调性,即不是第一个式子中的 \(k\) 具有单调性。
不巧,上面两个错我都各犯了一遍/dk/dk/dk。
如果非要说第一个式子中的,其实是 \(j-k\times i\) 这个整体具有单调性。(可恶(雾。
好吧,之后就是正常的套板子了。一般来说决策单调性的写法就是利用单调队列(不过这题好像可以用分治写,不仅好写,而且 不容易犯错)。
整个的时间复杂度上界是 \(O(cn\log n)\)。
Code
#include
#include
#include
using namespace std ;
#define int long long
const int N = 50005 ;
int n , k , f[N] , g[N] ;
vector < int > sc[305] , vc[305] ;
struct Node {
int l , r , p ;
Node ( int _l = 0 , int _r = 0 , int _p = 0 ) : l ( _l ) , r ( _r ) , p ( _p ) { }
} q[N] ;
inline bool cmp ( int a , int b ) {
return a > b ;
}
inline int calc ( int i , int x , int y ) {
if ( x < y )
return -1 ;
return g [ y ] + sc [ i ] [ ( x - y ) / i ] ;
}
bool cis[305] ;
int ot = 1 ;
signed main ( ) {
cin >> n >> k ;
for ( int i = 1 ; i <= n ; ++ i ) {
int x , y ;
cin >> x >> y ;
vc [ x ] .push_back ( y ) ;
}
for ( int i = 1 ; i <= 300 ; ++ i ) {
if ( vc [ i ] .empty ( ) )
continue ;
sort ( vc [ i ] .begin ( ) , vc [ i ] .end ( ) , cmp ) ;
int siz = vc [ i ] .size ( ) ;
sc [ i ] .push_back ( 0 ) ;
for ( int j = 0 ; j < siz ; ++ j )
sc [ i ] .push_back ( sc [ i ] [ j ] + vc [ i ] [ j ] ) ;
for ( int j = siz + 2 ; j <= k / i + 2 ; ++ j )
sc [ i ] .push_back ( sc [ i ] .back ( ) ) ;
}
int kp = 300 ;
for ( int i = 1 ; i <= kp ; ++ i ) {
if ( vc [ i ] .empty ( ) )
continue ;
for ( int j = 1 ; j <= k ; ++ j )
g [ j ] = f [ j ] ;
for ( int d = 0 ; d < i ; ++ d ) {
int head = 1 , tail = 1 ;
q [ tail ] = Node ( d , k , d ) ;
for ( int j = 0 , jd ; ( jd = j * i + d ) <= k ; ++ j ) {
while ( head <= tail && q [ head ] .r < jd )
++ head ;
if ( q [ head ] .l < jd )
q [ head ] .l = jd ;
int ps = 0 ;
while ( head <= tail && calc ( i , q [ tail ] .l , q [ tail ] .p ) < calc ( i , q [ tail ] .l , jd ) )
ps = q [ tail -- ] .l ;
if ( head <= tail && calc ( i , q [ tail ] .r , q [ tail ] .p ) < calc ( i , q [ tail ] .r , jd ) ) {
int l = q [ tail ] .l , r = q [ tail ] .r , mid ;
while ( l <= r ) {
mid = ( l + r ) >> 1 ;
if ( calc ( i , mid , q [ tail ] .p ) <= calc ( i , mid , jd ) )
ps = mid , r = mid - 1 ;
else
l = mid + 1 ;
}
q [ tail ] .r = ps - 1 ;
}
if ( ps )
q [ ++ tail ] = Node ( ps , k , jd ) ;
f [ jd ] = calc ( i , jd , q [ head ] .p ) ;
}
}
}
for ( int i = 1 ; i <= k ; ++ i )
cout << f [ i ] << " " ;
cout << "\n" ;
return 0 ;
}