BZOJ 3897: Power
3897: Power
Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 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
5 2 2
2 1
Sample Output
12
HINT
第一天用5的体力,接下来恢复2点体力,再用光。
Can<=10,N<=500000,E<=10^6.所有的输入非负,并且,V<=10^6。
Source
分析:
考虑我们一定是让大的权值尽量被提供较多的体力,并且要求不能溢出,那么考虑分治的思想,定义$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