BNU Watermelon Full of Water-单点更新,区间询问

题意:

         有n个点,每个点都有一个点权,每个点都有一条向右到达n点中一点的不等长线段。

总价值这样计算:

         若取这条线段,就将该线段的最左端点的权值放入总花费中。

求:

        能覆盖n个点的最小花费。

解:

    1.线段树+dp


            对于线段上的每一个点,都从左向右一一处理。对于点x,取所有能到达他的线段当中花费最小的。对于x后面的y也是一样的。这样就能保证以最小的权值去覆盖整个。但是,y能被1所覆盖是有条件的。就是必须取x点。方程是dp[i]=min(dp[i],dp[i-day-1]+a[i-day])

            反过来讲就是,每处理一个点i,就用自己dp[i-1]+p[i]去更新它之后的点。dp[i-1]表示到点i-1为止覆盖1~i-1的最小花费,dp[i-1]+p[i]即表示在第i天不要之前的了,重新买个西瓜,看看在这个西瓜的有效期限内能否使后面的花费变小。

            注意: 第一个点的权值是肯定包括在总花费里面的,因为经过他的线段只有一条。从题意上来说,第一天的西瓜必须买,不然那天就没得吃了。

            最后的答案就是取到最后一个点的最小价值。


            接下来就应该想如何用线段树来完成。用线段树处理有什么优势?

这题是单点更新,区间询问。  

            建树就是将每个点都赋值为无穷大。

            从左向右每处理一个点,是否真的有必要去更新这个点之后所能到达的所有点呢?

            可以不的。在处理第i个点时,只去更新这个点所能到达最远点的值,用i-1到n中的最小值(询问)加上i这点的花费去更新。

            用i-1到n中的最小值是因为在i-1那时,i-1到n这些点的最小值一定是覆盖1~(i-1) 的最小花费,即买到i-1这点所需最少花费;加上i这点的花费是因为加上才能覆盖最远点。

            感觉这样的解法很神奇

WA了n次,最后查出原因是因为输出写成I64d,写成lld就过了。wondering。

 2. 优先队列

     其实就是在弄出i-n的最小值的时候用个优先队列。方法是一样的。

#include <cstdio>
#include <algorithm>
#define maxn 50010
#define lson l,m, rt << 1
#define rson m + 1, r, rt << 1| 1
#define ls rt <<1
#define rs rt << 1 | 1
#define inf 5000000010
typedef long long LL;
using namespace std;
int n,p[maxn],t[maxn];
LL Min[maxn << 2],val;
void build(int l , int r, int rt){
    Min[rt] = inf;
    if(l == r) return;
    int m = (l + r) >> 1;
    build(lson);    build(rson);
}
void update(int &pos, LL &cc ,int l, int r, int rt){
    if(l == r){
        Min[rt] = min(Min[rt],cc);  return;
    }int m = (l + r) >> 1;
    if(pos <= m) update(pos,cc,lson);
    else update(pos,cc,rson);
    Min[rt] = min(Min[ls],Min[rs]);
}
LL query(int &L, int &R,int l, int r, int rt){
    if(L <= l && r <= R){
        return Min[rt];
    }int m = (l + r) >> 1;
    LL x = inf, y = inf;
    if(L <= m) x = query(L,R,lson);
    if(R > m) y = query(L,R,rson);
    return min(x,y);
}
int main(){
    while(scanf("%d",&n)!= EOF){
        for(int i = 1; i <= n; i ++)
            scanf("%d",&p[i]);
        for(int i = 1; i <= n; i ++)
            scanf("%d",t + i);
        build(1,n,1);   val = 0;
        for(int i = 1; i <= n; i ++){
            int lastday = i - 1 + t[i];
            lastday = lastday > n ? n: lastday;
            val += (LL)p[i];
            update(lastday,val,1,n,1);
            val = query(i,n,1,n,1);
        }printf("%lld\n",val);
    }
    return 0;
}

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#define N 50005
using namespace std;
long long Min[N<<2];
int p[N],last[N];
long long dp[N];
struct node{
    long val;
    int last;
    bool operator <(const node& cmp) const{
        return val > cmp.val;//注意,优先队列这里用的是>, 跟结构体数组是相反的。
    }
};
int main(){
    int n;

    while(scanf("%d",&n)!=EOF){
        for(int i = 1;i <= n; i ++)
            scanf("%d",&p[i]);
        for(int i = 1;i <= n; i ++)
            scanf("%d",&last[i]);
        priority_queue<node> q;
        node tmp;
        dp[1] = p[1];
        tmp.val = p[1]; tmp.last = last[1]; q.push(tmp);
        for(int i = 2; i <= n; i ++){
            tmp.val = dp[i - 1] + p[i];
            tmp.last = last[i] + i - 1;
            q.push(tmp);
            while(q.top().last < i) q.pop();
            dp[i] =  q.top().val;
        }
        printf("%lld\n",dp[n]);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值