[BZOJ3380]一套NOIP膜你题:小Q的新玩具

【问题描述】

期末考试完了,小Q得到了一件套新玩具,总共有N个零件。现在小Q想把新玩具搬回家里,可是他遭遇了新的问题:每个零件有自己的重量Wi,小Q要出租车把它们带回家。车每次只能运总重量和小于Lim的玩具,按照其中最重的玩具的重量收费。零件不能拆分成更小的部分。为了不打乱零件的顺序,增加自己拼装的难度,每次装车只能装连续的部分。现在想请你帮助小Q计算把玩具全部装回家的费用。


【输入】

第一行两个整数N和Limit。

接下来的N行,每行一个整数,代表第i个零件的的重量。


【输出】

第一行一个数字,表示答案。


【样例输入】
8 17
2
2
2
8
1
8
2

1


【样例输出】

12


【数据范围】
对于30%的数据,N和M<=1000

对于100%的数据,N和M<=300000题解:


题解:

dp[i]:以第i个零件为结尾的最小花费

方程式很好想:dp[i]=min{ dp[j]+max[j+1][i] }。max[l][r]的值就是区间L到R之间的Wi最大值。

可以预处理出来 max,然后O(n^2)的状态转移了,但这样一定超时。需要优化。


考虑决策点。

假如当前正在选择 dp[i]的决策点。对于每个i来说,转移点 j向左移动时,max[j+1][i]单调不递减。所以我们看可以维护一个决策点的队列q,满足区间和不大于Lim且 Wq1>Wq2>…>Wqi 且队列 q 中包含所有满足上述条件的决策点。那么,这里面的决策点一定有最优的决策点。

可能说的不是很明白,不懂的可以看:http://blog.csdn.net/qq_37816449/article/details/76096018


#include<cstdio>
#include<set>
using namespace std;
typedef long long LL;
const int N=300005;

int n, que[N];
LL Lim, w[N], sum[N], dp[N];
struct pque {
	multiset< LL > s;
	void push( LL v ) { s.insert( v ); }
	void del( LL v ) { s.erase( s.lower_bound(v) ); }
	LL top() { return *s.begin(); }
}pq;

int main() {
	scanf( "%d%lld", &n, &Lim );
	for( int i=1; i<=n; i++ )
		scanf( "%lld", &w[i] ), sum[i]=sum[i-1]+w[i];
		
	pq.push( dp[1]=w[1] );
	int l=1, r=0, left=1; que[ ++r ]=1;
	
    for( int i=2; i<=n; i++ ) {
		while( sum[i]-sum[left-1]>Lim && left<i ) {
			pq.del( w[ que[l] ]+dp[left-1] );
			if( que[l]==left ) l++, left++;
			else left++, pq.push( w[ que[l] ]+dp[left-1] );
		}
		while( l<=r && w[i]>=w[ que[r] ] ) {
			if ( l==r ) pq.del( w[ que[r] ]+dp[left-1] );
			else pq.del( w[ que[r] ]+dp[ que[r-1] ] );
			--r;
		}
		que[ ++r ]=i;
		if( l==r ) pq.push( w[i]+dp[left-1] );
		else pq.push( w[i]+dp[ que[r-1] ] );
		dp[i]=pq.top();
	}
	printf( "%lld\n", dp[n] );
	return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值