zoukankan      html  css  js  c++  java
  • [ZJOI2010]基站选址

    题目大意:

    (n) 个点,每个点 (i) 可以花费 (c_i) 建造基站,一共可以建不超过 (m) 个基站。若距离一个点 (i) 不超过 (S_i) 的距离内有基站,则视为这个点被覆盖了。若一个点 (i) 没有被覆盖,则需要付出 (w_i) 的代价。

    求最小费用?

    (m leq n, m leq 100, n leq 2e4, D_i leq 1e9, c_i leq 1e4, S_i leq 1e9, w_i leq 1e4)

    思路:

    首先有一个想法,就是我若在 (i) 建基站,且有 (j(j < i)) 没有被覆盖,若后面再在后面建基站,(j) 也不会被覆盖,简单的来讲就是后面的建基站不会对前面的状态有任何的改变,即没有后效性。

    所以可以想到一个dp,(dp[i][j]) 代表只考虑前 (i) 个,一共建了 (j) 个基站,且 (i) 一定建(方便转移)。

    转移:

    [dp[i][j] = min(dp[k][j-1] + c[i] + cost[k][i]) ]

    这里的 (cost[i][j]) 代表在 (i)(j) 建造基站(此时 (i)(j) 是最后两个基站),(i)(j) 之间没有覆盖点的 (sum w_k)

    枚举 (i) , (j), (k), (O(n^2*k)) ,计算 (cost[i][j]) 枚举 (i)(j) , (O(n)),

    一共 (O(n^3*k))

    但是计算 ((k,i)) 时,可以将 ((k + 1, i)) 直接取过来, 其实自己也不懂 。 反正就是可以 (O(n^2*k)) 然而并无卵用。。。

    现在的复杂度都堆在枚举 (k) 与计算 (cost[i][j]) 上,我们要想办法优化。

    首先,(dp[k][j - 1]) 是一个定值,会改变的只有 (cost[k][i]) , 我们来深入一下这个 (cost[k][i]) 的计算:显然,(cost[k][i])(i) 的增大单调不递增,如果某个点的可更新右端点算了好累啊 叫他们 (L_i, R_i) 好了 , 如果一个点的 (R_j) 正好等于 (i) , 那么之后所有点都不会再包含它,只有 (k) 才可以包含它, 但如果 (k) 都救不了它,那么它就必须去世了 。此时 (k) < (L_j) , 所以就区间更新一下 ((1, L_j - 1)) 给它们全部加上 (w_j) 即可。

    又因为我们还要计算 dp 的最小值,所以在更新的同时用线段树维护一下前缀最小值,并且把上述一开始枚举的 (1 dots m) 套在外面就好了。

    代码:

    #include <bits/stdc++.h>
     
    using namespace std;
     
    #define REP(i, a, n) for (register int i = a, _n = n; i <= _n; ++i)
    #define DREP(i, a, n) for (register int i = a, _n = n; i >= _n; --i)
    #define FOR(i, a, n) for (register int i = a, _n = n; i < _n; ++i)
    #define EREP(i, a) for (register int i = first[a]; i; i = edge[i].nxt)
    #define lson (p << 1)
    #define rson (p << 1 | 1)
    #define debug(x) cout << #x << " = " << x << endl
     
    char IO;
    inline int rd () {
        int res = 0;
        while ((IO = getchar()) && (IO < '0' || IO > '9'));
        while (IO >= '0' && IO <= '9') res = (res << 1) + (res << 3) + (IO ^ 48), IO = getchar();
        return res;
    }
     
    const int SIZE = 20005;
     
    int n, m;
     
    int d[SIZE], c[SIZE], s[SIZE], w[SIZE];
    int L[SIZE], R[SIZE];
    int ans = 2e9 + 500;
    
    int first[SIZE], ecnt;
    struct Edge {
        int v, nxt;
    } edge[SIZE];
    void Add_Edge (int u, int v) {
        ++ecnt;
        edge[ecnt].v = v;
        edge[ecnt].nxt = first[u];
        first[u] = ecnt;
    }
     
    int dp[SIZE];
     
    int x;
    int Mi[SIZE << 2], flag[SIZE << 2];
     
    inline void Down (int p) {
        if (flag[p] == 0) return;
        flag[lson] += flag[p];
        flag[rson] += flag[p];
        Mi[lson] += flag[p];
        Mi[rson] += flag[p];
        flag[p] = 0;
    }
    inline void Up (int p) {
        Mi[p] = min(Mi[lson], Mi[rson]);
    }
    void Build (int L, int R, int p) {
        flag[p] = 0;
        if (L == R) {
            Mi[p] = dp[L];
            return;
        }
        int mid = L + R >> 1;
        Build(L, mid, lson);
        Build(mid + 1, R, rson);
        Up(p);
    }
    void update (int L, int R, int p, int a, int l, int r) {
        if (L == l && R == r) {
            flag[p] += a;
            Mi[p] += a;
            return;
        }
        Down(p);
        int mid = L + R >> 1;
        if (r <= mid) update(L, mid, lson, a, l, r);
        else if (l > mid) update(mid + 1, R, rson, a, l, r);
        else update(L, mid, lson, a, l, mid), update(mid + 1, R, rson, a, mid + 1, r);
        Up(p);
    }
    int Query (int L, int R, int p, int a) {
        if (R == a) return Mi[p];
        Down(p);
        int mid = L + R >> 1;
        if (a <= mid) return Query (L, mid, lson, a);
        else return min(Mi[lson], Query(mid + 1, R, rson, a));
    }
     
    int main () {
        n = rd(), m = rd();
     
        int ans = 0;
        REP (i, 2, n) d[i] = rd();
        REP (i, 1, n) c[i] = rd();
        REP (i, 1, n) s[i] = rd();
        REP (i, 1, n) w[i] = rd(), ans += w[i];
     
        REP (i, 1, n) {
            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;
            Add_Edge (R[i], i);
        }
     
        memset(dp, 0x3f, sizeof dp); // 我们假设了i一定建站,所以j = 0时只能从dp[0]转移过来
        dp[0] = 0;
     
        REP (j, 1, m + 1) {
            Build(j - 1, n, 1);
            REP (i, j, n + 1) {
                if (i == n + 1) {
                    ans = min(ans, Query(j - 1, n, 1, n));  // 统计答案,此时线段树中存的是建j-1个站的答案
                    continue;
                }
                dp[i] = Query(j - 1, n, 1, i - 1) + c[i];
                EREP (k, i) {
                    Edge& e = edge[k];
                    if (L[e.v] > j - 1) update(j - 1, n, 1, w[e.v], j - 1, L[e.v] - 1);
                }
            }
        }
     
        printf ("%d
    ", ans);
     
        return 0;
    }
    
    

    还有一些细节,就是新加一个节点 (n + 1) 用它来记录答案,并且由于线段树中保存的是 (m - 1) 所以要枚举到 (m + 1)

    讲的有点糊,抱歉。

  • 相关阅读:
    冲刺一(5)
    冲刺一(4)
    冲刺一(3)
    构建之法阅读笔记之二
    冲刺一(2)
    冲刺一(1)
    第9周总结
    热词顶会分析
    第8周总结
    构建之法阅读笔记之一
  • 原文地址:https://www.cnblogs.com/ZPAYAUR/p/11172793.html
Copyright © 2011-2022 走看看