BZOJ 3897: Power

3897: Power

Time Limit: 30 Sec  Memory Limit: 512 MB
Submit: 218  Solved: 83
[Submit][Status][Discuss]

Description

我们假设小T有一个人体耐力上限E,在月初的时候,小T有E的体力。
接下来每一天有一个任务,这个任务小T可以付出任意的非负整数体力去完成,并且,每一天的结束的时候,小T会增加R的体力。当然体力是不可能超出E的,也就是说,如果当前体力+R大于E,那么恢复完之后的体力依旧是E。毫无疑问,体力是不可能小于0的。
每个任务会有一个价值V[],一个任务的收获就是这个任务的价值乘上付出的体力。
你要帮帮小T,使他最大化 “所有任务的收获之和”, 方便他继续的高富帅!
最后,我们的口号是“烧死GFS~”。

Input

第一行一个正整数case,表示数据的组数。
对于每一组数据,第一行有三个正整数E,R,N,表示的是能量上限,恢复值,和这个月的天数。第二行有N个非负整数表示V[1]-V[N]。

Output

对于一组数据,一行输出最大化的收获之和。

Sample Input

1
5 2 2
2 1

Sample Output

12

HINT

第一天用5的体力,接下来恢复2点体力,再用光。

Can<=10,N<=500000,E<=10^6.所有的输入非负,并且,V<=10^6。

Source

By 佚名提供

分析:

考虑我们一定是让大的权值尽量被提供较多的体力,并且要求不能溢出,那么考虑分治的思想,定义$f(l,r,be,en)$为从第l天到第r天,初始体力为be结束时体力为en的最优解,我们选择这个区间中权值最大的那个点x,如果这一天前面发那些天都休养生息还不能满足使得第x天的初始体力为E,那么前面几天就歇着吧,否则就是溢出了,溢出是浪费,所以就递归到前面那几天去贡献,后面几天也是一样的...

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn
using namespace std;

const int maxn=500000+5;

int n,E,R,cas,v[maxn],st[maxn][25];

inline bool cmp(int x,int y){
	return v[x]<v[y];
}

inline void init(void){
	for(int i=1;i<=n;i++)
		st[i][0]=i;
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j-1)<=n;i++)
			st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1],cmp);
}

inline int query(int x,int y){
	if(x>y)
		swap(x,y);
	int len=y-x+1,k;
	for(k=20;k>=0;k--)
		if(((len>>k)&1)||k==0)
			break;
	return max(st[x][k],st[y-(1<<k)+1][k],cmp);
}

inline long long solve(int l,int r,int be,int en){
	if(l>r)
		return 0;
	int id=query(l,r);
	long long ans=0,tmp=1LL*(id-l)*R;
	if(1LL*be+1LL*tmp>E)
		ans=solve(l,id-1,be,E),be=E;
	else
		be+=tmp;
	tmp=1LL*(r-id+1)*R;
	if(tmp<en)
		en-=tmp;
	else
		ans+=solve(id+1,r,R,en),en=0;
	ans+=1LL*(be-en)*v[id];
	return ans;
}

signed main(void){
	scanf("%d",&cas);
	while(cas--){
		scanf("%d%d%d",&E,&R,&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&v[i]);
		init();printf("%lld\n",solve(1,n,E,0));
	}
	return 0;
}

  


By NeighThorn

posted @ 2017-03-07 21:08  NeighThorn  阅读(360)  评论(0编辑  收藏  举报