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

来源:互联网 发布:查淘宝买家退货率 编辑:程序博客网 时间:2024/05/16 09:16

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|1using 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;}
阅读全文
0 0
原创粉丝点击