有 \(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\n", ans);
return 0;
}
还有一些细节,就是新加一个节点 \(n + 1\) 用它来记录答案,并且由于线段树中保存的是 \(m - 1\) 所以要枚举到 \(m + 1\) 。
讲的有点糊,抱歉。
原文:https://www.cnblogs.com/ZPAYAUR/p/11172793.html