珠宝


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 ;
}