3380 (NOIP 膜你题2 T3) 小Q的新玩具

 

说明:

 

辣鸡出题人,毁我比赛,颓我精神,耗我钱财,废我青春。

 

样例数据: 

 

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]);
}

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值