BZOJ1835 [ZJOI2010]base 基站选址

来源:互联网 发布:安卓好用的倒计时软件 编辑:程序博客网 时间:2024/05/16 08:33

Description

有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di。需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci。如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就成它被覆盖了。如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi。现在的问题是,选择基站的位置,使得总费用最小。

Input

输入数据 (base.in) 输入文件的第一行包含两个整数N,K,含义如上所述。 第二行包含N-1个整数,分别表示D2,D3,…,DN ,这N-1个数是递增的。 第三行包含N个整数,表示C1,C2,…CN。 第四行包含N个整数,表示S1,S2,…,SN。 第五行包含N个整数,表示W1,W2,…,WN。

Output

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

Sample Input

3 2 1 2 2 3 2 1 1 0 10 20 30

Sample Output

4

Data Range

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

Problem Address

BZOJ1835 洛谷P2605

Solution DP + 线段树优化

  • f[i][j]表示在第i个村庄修建第j个基站且不考虑第i+1~n个村庄所需的最小费用。
  • 则转移方程为f[i][j]=Min(f[k][j1]+cst[k][i])+c[i](j1k<i)。其中cst[k][i]表示第i~k个村庄之间没有被基站i,k覆盖的村庄所需的赔偿费用,计算费用的复杂度为O(n),则总复杂度为O(n2k)
  • 这样显然是不能通过的,我们考虑如何优化:
  • 首先我们发现之前的转移方程可以去掉一维j,实际上只要在最外层枚举j就可以了,也就是f[i]=Min(f[k]+cst[k][i])+c[i](j1k<i)
  • 而主要的消耗在计算cst[k][i]上,也就是有多少个村庄需要赔偿。
  • 对于任意一个村庄i,记它所能被覆盖的左右边界st[i],ed[i]最左端、最右端可以覆盖到i的基站位置,可用二分查找处理),然后在用邻接表记录ed值为i的村庄有哪些,在这些村庄之前建立基站就覆盖不到i了。
  • 这样当我们推导i+1时,若从村庄1~st[k]1(ed[k]=i)转移过来则必定要赔偿村庄k的费用,我们就可以考虑用线段树来维护f[k]+cst[k][i]的值,即在区间[1,st[k]1]加上村庄k的费用,而转移即在区间[1,i1]f[k]+cst[k][i]的最小值,总复杂度为O(nlogn×k)

Code

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#define sL (s << 1)#define sR (s << 1 | 1)using namespace std;const int Maxn = 0x3f3f3f3f;const int N = 2e4 + 5, M = N << 2;int d[N], c[N], w[N], s[N], st[N], ed[N], f[N]; int n, k, Ans, val[M], tag[M];struct point{    int to; point *nxt;}a[M], *T = a, *lst[N]; inline void addEdge(const int &x, const int &y) {T->nxt = lst[x]; T->to = y; lst[x] = T++;} template <class T> inline T Min(const T &a, const T &b) {return a < b? a : b;}template <class T> inline void CkMin(T &a, const T &b) {if (a > b) a = b;}inline int get(){    char ch; bool f = false; int res = 0;    while (((ch = getchar()) < '0' || ch > '9') && ch != '-');    if (ch == '-') f = true;     else res = ch - '0';    while ((ch = getchar()) >='0' && ch <= '9')        res = (res << 3) + (res << 1) + ch - '0';    return f? ~res + 1 : res;}inline void put(int x){    if (x < 0)      x = ~x + 1, putchar('-');    if (x > 9) put(x / 10);    putchar(x % 10 + 48);}inline void Push(const int &s) {val[s] = Min(val[sL], val[sR]);}inline void Add(const int &s, const int &z) {val[s] += z; tag[s] += z;}inline void Down(const int &s){    if (!tag[s]) return ;    Add(sL, tag[s]); Add(sR, tag[s]); tag[s] = 0;}inline void Build(const int &s, const int &l, const int &r){    tag[s] = 0;    if (l == r) return (void)(val[s] = f[l]);    int mid = l + r >> 1;    Build(sL, l, mid); Build(sR, mid + 1, r);    Push(s);}inline int Query(const int &s, const int &l, const int &r, const int &x, const int &y){     if (l == x && r == y) return val[s];    Down(s); int mid = l + r >> 1;     if (y <= mid) return Query(sL, l, mid, x, y);     else if (x > mid) return Query(sR, mid + 1, r, x, y);      else return Min(Query(sL, l, mid, x, mid),                      Query(sR, mid + 1, r, mid + 1, y));}inline void Modify(const int &s, const int &l, const int &r, const int &x, const int &y, const int &z){    if (l == x && r == y) return Add(s, z);    Down(s); int mid = l + r >> 1;    if (y <= mid) Modify(sL, l, mid, x, y, z);     else if (x > mid) Modify(sR, mid + 1, r, x, y, z);      else       {        Modify(sL, l, mid, x, mid, z);        Modify(sR, mid + 1, r, mid + 1, y, z);      }    Push(s);}int main(){    n = get(); k = get() + 1;    for (int i = 2; i <= n; ++i) d[i] = get();    for (int i = 1; i <= n; ++i) c[i] = get();    for (int i = 1; i <= n; ++i) s[i] = get();    for (int i = 1; i <= n; ++i) w[i] = get();    ++n; d[n] = w[n] = Maxn;      //当我们推导i时,我们只考虑了它和前面的基站产生的影响    //这时对于最后一个基站我们不会考虑它和之后的村庄产生的影响    //则我们可以在最后增加一个村庄    //保证它必定被作为基站(无建设费用)且不对前面产生影响    //这样就不会有遗漏的了     for (int i = 1; i <= n; ++i)    {        st[i] = lower_bound(d + 1, d + n + 1, d[i] - s[i]) - d;        ed[i] = lower_bound(d + 1, d + n + 1, d[i] + s[i]) - d;        if (d[ed[i]] > d[i] + s[i]) ed[i]--; addEdge(ed[i], i);        //lower_bound查找的是大于等于x的第一个数        //而ed[i]要求的是小于等于x的最后一个数        //所以判断一下减一就可以了     }    for (int i = 1; i <= k; ++i)    if (i == 1)    {        int res = 0;        for (int j = 1; j <= n; ++j)        {            f[j] = res + c[j];            for (point *e = lst[j]; e; e = e->nxt)             res += w[e->to];        }        Ans = f[n];    }    else     {        Build(1, 1, n); int y;        for (int j = 1; j <= n; ++j)        {            //注意线段树区间的边界条件            f[j] = (j > i - 1 ? Query(1, 1, n, i - 1, j - 1) : 0) + c[j];            for (point *e = lst[j]; e; e = e->nxt)             if (st[y = e->to] > 1) Modify(1, 1, n, 1, st[y] - 1, w[y]);            //这里其实只要修改区间[i, st[y] - 1]就行了            //不过询问/修改的区间长对于线段树其实更快         }        CkMin(Ans, f[n]);    }    return put(Ans), 0;}
原创粉丝点击