题目大意:
有 (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) 一定建(方便转移)。
转移:
这里的 (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) 。
讲的有点糊,抱歉。