[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)

讲的有点糊,抱歉。

原文地址:https://www.cnblogs.com/ZPAYAUR/p/11172793.html