说明:
辣鸡出题人,毁我比赛,颓我精神,耗我钱财,废我青春。
样例数据:
8 17
2 2 2 8 1 8 2 1
答案:
12
这题先找思路,发现是DP(毕竟状态好找)
设f[i]=前i个玩具运完的最小费用
设biggestnum(a,b)为闭区间[a,b]中重量最高值。
有转移方程f[i]=min(f[j]+biggestnum(j+1,i));//意味着j+1到i的再用一辆车。
有两个问题:
1.biggestnum如何求,如何高效求?
2.这个状态转移是O(n),总时间为O(n^2),以本题数据范围(300000)肯定超时,如何优化?
解决方案
1:biggestnum(a,b)可以用线段树一类数据结构和算法算出所有的,时间复杂度为O(nlogn)可以接受,但是我们没有必要算出所有的biggestnum,只需要算出关键地方的。
2.所谓关键地方,优化,我们需要对这个DP式子进行分析:
不难看出,f是一个不下降的序列,所以,如果biggestnum(j+1,i)==biggestnum(j,i),
则f[j]+biggestnum(j+1,i)>=f[j-1]+biggestnum(j,i),所以,若数列中seq[j]<=biggestnum(j+1,i);则f[j]+biggestnum(j+1,i)>=f[j-1]+biggestnum(j,i),所以f[j]+biggestnum(j+1,i)不是最优的,j-1比他更优,那么最优的是哪个呢?
j不能再减少时,
有两种情况,一是biggestnum(j+1,i)< biggestnum(j,i)时的j,二是j到i的玩具总重超过限制时的j。
第二种情况的j可以用lower-bound计算,
第一种情况可以用单调队列:
维护一个数值递减的队列,将seq[1]~seq[n]依次放入,当队列尾部的值小于等于当前要放的值时将队尾元素放弃,直到队尾的值大于当前要放的值是,将他放入队尾。
这样数列中每一个数a前面的数b即为在原数列中从a的位置从右往左数第一个大于a的数。
这时b的位置既是最优,再往左最大数就会变成b而不是a,需要重新讨论。
所以对于单调队列q,
f[i]=min(f[q[i-1].id]+seq[q[i].id]);( sum(q[i-1].id~i)<限制,每次判断单调队列队头满不满足,不满足就删去 )//q[i].id指在原数列q[i]的位置,相当于一个结构体。
可是单调队列里面有多个数,不可能每换一个i就搜一遍q,那样最坏情况也是O(n^2)的
于是我们需要高效找出q中的最小值,并在修改q时,高效的修改最小值。
这就意味着我们需要一个算法或数据结构来维护他。
可以用set,总耗费时间为O(nlogn)。具体就是单调队列加入一个元素,就s.insert,
删除一个就s.erase,利用STL,这题其实并不怎么难。(如果没有set,就要在AVL,Splay,Treap,Binary_Tree中选一个,实际上我哪个也不想写)。
最后的答案为两种情况中选较小值。(中途可能会爆int)
另外,POJ3017和这题差不多,只是多了没有可行方案时输出-1罢了。
#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
int n,a[300055],head,tail,minn;
long long sum[300055],dp[300055],lim;
struct node{
int num,id,setnum;
node(){}
node(int a,int b,int c):num(a),id(b),setnum(c){}
}q[300055];
int main(){
multiset<long long>s;
int tmp;
scanf("%d%lld",&n,&lim);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]),
sum[i]=sum[i-1]+a[i];
if(a[i]>lim){
puts("-1");
return 0;
}
}
head=tail=0;
for(int i=1;i<=n;i++){
while(head<tail && q[tail-1].num <= a[i]){
if(tail-1>head)
s.erase(q[tail-1].setnum);
tail--;
}
tmp=lower_bound(sum,sum+1+n,sum[i]-lim)-sum;
if(tail>head){
q[tail++]=node(a[i],i,a[i]+dp[q[tail-1].id]);
s.insert(q[tail-1].setnum);
}
else q[tail++]=node(a[i],i,0);
while(head<tail && sum[i]-sum[q[head].id]>lim){
if(head<tail-1)
s.erase(q[head+1].setnum);
head++;
}
dp[i]=dp[tmp]+q[head].num;
if(head<tail-1 && (!s.empty())) dp[i]=min(dp[i],*s.begin());
}
printf("%lld",dp[n]);
}