BZOJ 1835 base 基站选址(DP 线段树)

1835: [ZJOI2010]base 基站选址

Time Limit: 100 Sec Memory Limit: 64 MB

有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di。需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci。如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就成它被覆盖了。如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi。现在的问题是,选择基站的位置,使得总费用最小。 输入数据 (base.in) 输入文件的第一行包含两个整数N,K,含义如上所述。 第二行包含N-1个整数,分别表示D2,D3,…,DN ,这N-1个数是递增的。 第三行包含N个整数,表示C1,C2,…CN。 第四行包含N个整数,表示S1,S2,…,SN。 第五行包含N个整数,表示W1,W2,…,WN。
Input

输出文件中仅包含一个整数,表示最小的总费用。
Output

3 2 1 2 2 3 2 1 1 0 10 20 30
Sample Input

4

Sample Output

40%的数据中,N<=500;

100%的数据中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。

思路:
设f[i][j]表示第i个村建第j个基站的最小花费,转移方程:
f[i][j]=min{ f[k][j-1]+cost(k,i) } + c[i] ,j-1<=k<=i-1
cost(k,i)=sigma{ w[x] } k+1<=x<=i-1 , 且x未被覆盖
我们很容易发现其实j这一位是可以省去的,然而尽管如此我们还要继续降次。
方式很明确,就是维护一下min{ f[k][j-1]+cost(k,i) } ,可以用线段树维护
最外层循环j,考虑每一层i。
L[i],R[i]分别表示在i左右覆盖的范围,
假设我们已经求完了f[i][j]要求f[i+1][j],考虑那些恰可以被i覆盖到而不能被i+1覆盖到的,
即满足R[x]=i+1的点,将[1~y](R[y]=x)区间内的线段树值都加w[x],
因为前一个基站k位于[1~y](R[y]=x),也就是当f[k](1 <= k <= y,最后一个基站在k点时,无法覆盖到x),那么点x因不会被覆盖到需要做出赔偿(+w[x])。
求f[i]就是求区间[1~i-1]内线段树维护的最小值。
其中L[i],R[i]直接用二分函数求。
线段树支持区间操作区间查询。
总的时间复杂度为O(nmlogn)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define lc u<<1
#define rc u<<1|1
using namespace std;
const int N = 20005, INF = 1e9 + 5;

int n, k, d[N], c[N], s[N], w[N];
int L[N], R[N], f[N];
int head[N], idc;

struct edge{
    int to, next;
}ed[N];

void adde(int u, int v){
    ed[++idc].to = v;
    ed[idc].next = head[u];
    head[u] = idc;
}

struct node{
    int mn, flag;
}tree[N<<2];

void updata(int u){
    tree[u].mn = min(tree[lc].mn, tree[rc].mn);
}

void pushdown(int u){
    if(tree[u].flag){
        int addd = tree[u].flag;
        tree[lc].flag += addd;
        tree[lc].mn += addd;
        tree[rc].flag += addd;
        tree[rc].mn += addd;
        tree[u].flag = 0;
    }
}

void build(int u, int l, int r){
    tree[u].flag = 0;
    if(l == r){
        tree[u].mn = f[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(lc, l, mid), build(rc, mid+1, r);
    updata(u);
}

void modify(int u, int l, int r, int ll, int rr, int val){
    if(ll > rr) return;
    if(ll<=l && r<=rr) {
        tree[u].flag += val;
        tree[u].mn += val;
        return;
    }
    pushdown(u);
    int mid = (l + r) >> 1;
    if(ll <= mid) modify(lc, l, mid, ll, rr, val);
    if(mid < rr) modify(rc, mid+1, r, ll, rr, val);
    updata(u);
}

int query(int u, int l, int r, int ll, int rr){
    if(ll > rr) return 0;
    if(ll<=l && r<=rr) return tree[u].mn;
    pushdown(u);
    int mn = INF;
    int mid = (l + r) >> 1;
    if(ll <= mid) mn = min(mn, query(lc, l, mid, ll, rr));
    if(mid < rr) mn = min(mn, query(rc, mid+1, r, ll, rr));
    return mn;
}

void dp(){
    int ans = INF, cc = 0;
    for(int i=1; i<=n; i++){//j==1时 
        f[i] = cc + c[i];
        for(int k=head[i]; k; k=ed[k].next)//寻找恰好被i-1覆盖,不被i覆盖的点 
            cc += w[ed[k].to];//之后也永远不会再覆盖了,所以可以累加 
    }
    for(int j=2; j<=k; j++){
        build(1, 1, n);//基站数目变化了,所以线段树也要重新建 
        for(int i=1; i<=n; i++){
            f[i] = query(1,1,n,1,i-1) + c[i];//转移 
            for(int k=head[i]; k; k=ed[k].next){
                int v = ed[k].to;
                modify(1, 1, n, 1, L[v]-1, w[v]);//新增的且永远不会再覆盖的点,加上代价 
            }
        }
        ans = min(ans, f[n]);
    }
    printf("%d", ans);
}

int main() {
    scanf("%d%d", &n, &k);
    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]);
    n++; k++;
    d[n]=INF; w[n]=INF;
    for(int i=1; i<=n; i++){
        L[i] = lower_bound(d+1, d+1+n, d[i]-s[i]) - d;
        R[i] = lower_bound(d+1, d+1+n, d[i]+s[i]) - d;
        if(d[R[i]] - d[i] > s[i]) R[i]--;//覆盖范围是一个开区间 
        adde(R[i],i);//建边,为了寻找恰好被i-1覆盖,不被i覆盖的点
    }
    dp();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值