bzoj1835/lg2605:[ZJOI2010]基站选址 (线段树优化dp)

17 篇文章 0 订阅
3 篇文章 0 订阅

哇..假的假的…好难….
为什么…这么多道题..我要先看这道……….
我以为第一道会很和蔼的啊啊啊

可怕的..lg2605

唉…

其实..简单来说就是线段树优化dp….但是…依旧不会啊扎心

方程其实也是可以写的 f(i,j)=f(k,j1)+cost(k+1,i1)+c[i]f(i,j) f ( i , j ) = f ( k , j − 1 ) + c o s t ( k + 1 , i − 1 ) + c [ i ] , f ( i , j ) 表示在第i个点建立第j个通讯基站
然后去想怎么优化….cost..是最难求的…

每个点会有一个覆盖区域,那我们可以预处理求出来他的左右端点

右端点为i的时候,在i+1的时候右边显然就不能被覆盖了,那么如果上一个基地建在他的左端点的右边,那么他就再也不能被覆盖了。因此在线段树中左端点右边的位置加上他的不能被覆盖值
每一个 f(i,j) f ( i , j ) 也就是前面最小的..线段树求个最小值就好啦

注:因为我们无法判断最后一个建在哪里,所以有一个小优化吧,建一个n+1点,k++,建他的费用为0,w、d都给inf。那么无论如何想要最大都会有这个点

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 20010
#define inf 0x3f3f3f3f
int n,m,d[N],c[N],s[N],w[N],L[N],R[N],h[N],dp[N];
struct node{int x,f;}tree[N<<2];
struct node1{int to,next;}e[N];
void pushup(int v){tree[v].x=min(tree[v<<1].x,tree[v<<1|1].x);}
void pushdown(int v){
    if(!tree[v].f) return;
    tree[v<<1].f+=tree[v].f;tree[v<<1|1].f+=tree[v].f;
    tree[v<<1].x+=tree[v].f;tree[v<<1|1].x+=tree[v].f;
    tree[v].f=0;
}
void build(int v,int l,int r){
    tree[v].f=0;
    if(l==r){
        tree[v].x=dp[l];
        return;
    }int mid=l+r>>1;
    build(v<<1,l,mid);build(v<<1|1,mid+1,r);
    pushup(v);
}
void add(int v,int l,int r,int x,int y,int z){
    if(x>y) return;
    if(x<=l && r<=y){
        tree[v].x+=z;tree[v].f+=z;
        return;
    }int mid=l+r>>1;pushdown(v);
    if(x<=mid) add(v<<1,l,mid,x,y,z);
    if(mid<y) add(v<<1|1,mid+1,r,x,y,z);
    pushup(v);
}
int query(int v,int l,int r,int x,int y){
    if(x>y) return 0;
    if(x<=l && r<=y) return tree[v].x;
    int mid=l+r>>1,s=inf;pushdown(v);
    if(x<=mid) s=min(query(v<<1,l,mid,x,y),s);
    if(mid<y) s=min(query(v<<1|1,mid+1,r,x,y),s);
    return s;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=2;i<=n;i++) scanf("%d",&d[i]);
    for(int i=1;i<=n;i++) scanf("%d",&c[i]);
    for(int i=1;i<=n;i++) scanf("%d",&s[i]);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    d[++n]=inf;c[n]=0;w[n]=inf;m++;
    for(int i=1;i<=n;i++){
        L[i]=lower_bound(d+1,d+n+1,d[i]-s[i])-d;
        R[i]=upper_bound(d+1,d+n+1,d[i]+s[i])-d-1;
        e[i].to=i;e[i].next=h[R[i]];h[R[i]]=i;
    }
    int tmp=0;for(int i=1;i<=n;i++){
        dp[i]=tmp+c[i];
        for(int j=h[i];j;j=e[j].next) tmp+=w[e[j].to];
    }int ans=dp[n];
    for(int j=2;j<=m;j++){
        build(1,1,n);memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){
            dp[i]=query(1,1,n,1,i-1)+c[i];
            for(int k=h[i];k;k=e[k].next) add(1,1,n,1,L[e[k].to]-1,w[e[k].to]);
        }
        ans=min(dp[n],ans);
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值