[loj6039][雅礼集训 2017 Day5]珠宝——决策单调性+分治

###题目大意:
Miranda 准备去市里最有名的珠宝展览会,展览会有可以购买珠宝,但可惜的是只能现金支付,Miranda 十分纠结究竟要带多少的现金,假如现金带多了,就会比较危险,假如带少了,看到想买的右买不到。展览中总共有 N N N种珠宝,每种珠宝都只有一个,对于第 i i i种珠宝,它的售价为 C i C_i Ci​万元,对 Miranda 的吸引力为 V i V_i Vi​​ 。
Miranda 总共可以从银行中取出 K K K万元,现在她想知道,假如她最终带了 i i i万元去展览会,她能买到的珠宝对她的吸引力最大可以是多少?
###思路:
每个物品的重量很小,于是按照重量分类,对于同一个重量里面的物品一起考虑。
不难发现,贪心的选择物品,对于同一个重量,选择的物品一定是按照价值从大到小排序的一个前缀。
于是对于每一种重量的决策,体积也按照剩余类分类,不同剩余类的dp分开考虑,此时dp可以看成是连续的。
因为选择的是一个前缀,并且前缀和的斜率不断减小,不难发现满足决策单调性,简单地证明一下:
i &lt; j i&lt;j i<j i i i的决策点为 p p p,即有任意 s &lt; p s&lt;p s<p满足 d p [ s ] + s u m [ s , i ] &lt; d p [ p ] + s u m [ p , i ] dp[s]+sum[s,i]&lt;dp[p]+sum[p,i] dp[s]+sum[s,i]<dp[p]+sum[p,i],即 s u m [ s , i ] − s u m [ p , i ] &lt; d p [ p ] − d p [ s ] sum[s,i]-sum[p,i]&lt;dp[p]-dp[s] sum[s,i]sum[p,i]<dp[p]dp[s],又因为有 s u m [ s , j ] − s u m [ p , j ] &lt; s u m [ s , i ] − s u m [ p , i ] sum[s,j]-sum[p,j]&lt;sum[s,i]-sum[p,i] sum[s,j]sum[p,j]<sum[s,i]sum[p,i],所以有 d p [ s ] + s u m [ s , j ] &lt; d p [ p ] + s u m [ p , j ] dp[s]+sum[s,j]&lt;dp[p]+sum[p,j] dp[s]+sum[s,j]<dp[p]+sum[p,j]
于是直接分治优化决策单调性就好了,所谓分治大概就是不断地确定一个区间中点的决策点,这样以后对于左区间和右区间便各自拥有了上界和下界,然后不断地递归就好了。
时间复杂度 Θ ( 300 m log ⁡ m ) \Theta(300m\log{m}) Θ(300mlogm)

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
typedef long long ll;

using namespace std;

void File(){
	freopen("loj6039.in","r",stdin);
	freopen("loj6039.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e6+10;
const int maxm=5e4+10;
const int maxc=300+10;
const ll inf=LLONG_MAX>>1;
int n,m,lst[maxm],tot;
ll dp[maxc][maxm];
vector<ll>cost[maxc];
vector<ll>sum[maxc];

void init(){
	read(n); read(m);
	ll c,v;
	REP(i,1,n)read(c),read(v),cost[c].push_back(v);
	REP(i,1,300)sort(cost[i].begin(),cost[i].end());
	REP(i,1,300){
		ll las=0ll;
		sum[i].push_back(las);
		DREP(j,cost[i].size()-1,0){
			las=las+cost[i][j];
			sum[i].push_back(las);
		}
	}
}

ll calc(int len,int pre,int now){
	int cnt=(now-pre)/len;
	if(cnt>(int)(sum[len].size()-1))return -inf;
	return dp[len-1][pre]+sum[len][cnt];
}

//c_max*m*log(m)
void divide(int len,int l,int r,int L,int R){
	int mid=(l+r)>>1,pos=0; ll Max=0;
	REP(i,L,min(R,mid)){
		ll val=calc(len,lst[i],lst[mid]);
		if(val>Max)Max=val,pos=i;
	}
	if(l==r){dp[len][lst[l]]=Max;return;}
	divide(len,l,mid,L,pos);
	divide(len,mid+1,r,pos,R);
}

void work(){
	REP(i,1,300){
		REP(j,0,i-1){
			lst[tot=1]=j;
			while(lst[tot]+i<=m)lst[tot+1]=lst[tot]+i,++tot;
			divide(i,1,tot,1,tot);
		}
	}
	REP(i,1,m)printf("%lld%c",dp[300][i],i==m ? '\n' : ' ');
}

int main(){
	//File();
	init();
	work();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值