bzoj1835: \[ZJOI2010\]base 基站选址 线段树优化

bzoj1835: [ZJOI2010]base 基站选址

Description

有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。
HINT

2018.7.7重设时限为20s,未重测!

分析

一道线段树优化Dp好题
首先方程不难写出
f [ k + 1 ] [ i ] = min ⁡ { f [ k ] [ j ] + w ( j , i ) } + c [ i ] f[k+1][i]=\min \{f[k][j] + w(j,i)\}+c[i] f[k+1][i]=min{f[k][j]+w(j,i)}+c[i]
其中 f [ k ] [ i ] f[k][i] f[k][i]表示建了 k k k个基站,前 i i i村庄, i i i处建基站的最小代价, w ( j , i ) w(j,i) w(j,i)表示 i , j i,j i,j建立基站的前提下, j j j i i i的村庄还需要多少抚恤金。
考虑优化这个方程。关键在于如何处理 w w w
可以首先简化这个问题,先不考虑后面的基站对村庄的影响。
考虑暴力
我们在扫 i i i的时候把每个村庄的抚恤金暴力加入每个 f [ k ] [ j ] f[k][j] f[k][j]上。
这个时候注意到每一个村庄的抚恤金,只会对不在其范围内的 f [ k ] [ j ] f[k][j] f[k][j]产生贡献。
也就是说,每个村庄对 f [ k ] f[k] f[k]产生的贡献都是一段区间。
这样子的话把之前的 f [ k ] f[k] f[k]加入线段树,这样子的话可以把每个村庄的贡献转化成区间加,当前答案转化成区间最小值询问。
这个时候再考虑后面的基站对村庄的影响。
如果当前做到的 i i i还在村庄的范围之内的话,这个村庄的抚恤金不需要加入线段树中。
而每次把 i i i挪到 i + 1 i + 1 i+1都可能有若干个村庄要产生贡献。
这个过程可以使用堆来维护。具体地,把所有村庄按照范围右端点插入一个小根堆,一旦某个 i i i比堆顶大,那么把这个村庄弹出堆顶加入其贡献即可。
复杂度 O ( k n l o g n ) O(knlogn) O(knlogn)
非常优秀的一道题。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
const int Nt = 32767, N = 2e4 + 10, inf = 0x3f3f3f3f;
int ri() {
    char c = getchar(); int x = 0; for(;c < '0' || c > '9'; c = getchar()) ;
    for(;c >= '0' && c <= '9';  c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x;
}
int n, K, id[N], g[N], d[N], c[N], s[N], w[N], L[N], R[N], f[101][N], mn[N << 2], t[N << 2];
struct P {int u, x;}T[Nt << 1];
P min(P a, P b) {return a.x < b.x ? a : b;}
void Up(int i, int x) {for(T[i += Nt].x = x; i >>= 1; T[i] = min(T[i << 1], T[i << 1 | 1])) ;}
void In(int *f, int s = 1) {for(int i = s;i <= n; ++i) f[i] = ri();}
bool cmpr(int a, int b) {return d[a] + s[a] <= d[b] + s[b];}
bool cmpl(int a, int b) {return d[a] - s[a] <= d[b] - s[b];}
void Build(int p, int L, int R, int k) {
    t[p] = 0;
    if(L == R) return void(mn[p] = f[k][L]);
    int m = L + R >> 1; Build(p << 1, L, m, k); Build(p << 1 | 1, m + 1, R, k);
    mn[p] = std::min(mn[p << 1], mn[p << 1 | 1]);
}
void Tag(int p, int v) {mn[p] += v; t[p] += v;}
void Push(int p) {if(t[p]) Tag(p << 1, t[p]), Tag(p << 1 | 1, t[p]), t[p] = 0;}
void Add(int p, int L, int R, int st, int ed, int v) {
    if(L == st && ed == R) return Tag(p, v);
    if(t[p]) Push(p);
    int m = L + R >> 1;
    if(st <= m) Add(p << 1, L, m, st, std::min(m, ed), v);
    if(ed > m)  Add(p << 1 | 1, m + 1, R, std::max(st, m + 1), ed, v);
    mn[p] = std::min(mn[p << 1], mn[p << 1 | 1]);
}
int Que(int p, int L, int R, int st, int ed) {
    if(L == st && ed == R) return mn[p];
    if(t[p]) Push(p);
    int m = L + R >> 1, r = inf;
    if(st <= m) r = std::min(r, Que(p << 1, L, m, st, std::min(m, ed)));
    if(ed > m) r = std::min(r, Que(p << 1 | 1, m + 1, R, std::max(st, m + 1), ed));
    return r;
}
int main() {
    n = ri(); K = ri();
    In(d, 2); In(c); In(s); In(w); int ans = 0;
    for(int i = 1;i <= n; ++i) id[i] = i, ans += w[i];
    std::sort(id + 1, id + n + 1, cmpl);
    int t = n;
    for(int i = n; i; L[id[i--]] = t) 
        for(int l = d[id[i]] - s[id[i]]; d[t - 1] >= l && t > 1; --t) ;
    std::sort(id + 1, id + n + 1, cmpr);
    t = 1; 
    for(int i = 1;i <= n; R[id[i++]] = t) 
        for(int r = d[id[i]] + s[id[i]];d[t + 1] <= r && t < n; ++t) ;
    std::memset(T, 0x3f, sizeof(T));
    for(int i = 1;i <= n; ++i) T[i + Nt].u = i;
    for(int i = 1, r = 0;i <= n; ++i) {
        for(;T[1].x < i;) r += w[T[1].u], Up(T[1].u, inf);
        g[i] = f[1][i] = r + c[i];
        Up(i, R[i]);
    }
    for(int k = 1; k < K; ++k) {
        for(;T[1].x != inf;) Up(T[1].u, inf);
        Build(1, 1, n, k);
        for(int i = k + 1;i <= n; ++i) {
            for(;T[1].x < i;) {
                if(L[T[1].u] - 1) Add(1, 1, n, 1, L[T[1].u] - 1, w[T[1].u]);
                Up(T[1].u, inf);
            }
            f[k + 1][i] = Que(1, 1, n, k, i - 1) + c[i];
            Up(i, R[i]); g[i] = std::min(g[i], f[k + 1][i]);
        }
    }
    for(;T[1].x != inf;) Up(T[1].u, inf);
    for(int i = n, r = 0; i; --i) {
        for(;-T[1].x > i;) r += w[T[1].u], Up(T[1].u, inf);
        ans = std::min(ans, g[i] + r);
        Up(i, -L[i]);
    }
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值