【数据结构优化DP】luogu_P2605 [ZJOI2010]基站选址
题意
有\(N\)个村庄坐落在一条直线上,第\(i(i>1)\)个村庄距离第\(1\)个村庄的距离为\(D_i\)。
需要在这些村庄中建立不超过\(K\)个通讯基站,在第\(i\)个村庄建立基站的费用为\(C_i\)。
如果在距离第\(i\)个村庄不超过\(S_i\)的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第\(i\)个村庄没有被覆盖,则需要向他们补偿,费用为\(W_i\)。
现在的问题是,选择基站的位置,使得总费用最小。
思路
设\(f_{i,j}\)为在村庄\(j\)建第\(i\)个基站时的最小费用(不考虑\(j+1\sim n\)村庄),则有:
\(f_{i,j}=min\{f_{i-1,k}+val(k,j)\}+c_j\),其中\(val(k,j)\)为在\(k\)和\(j\)之间不建基站时要补偿的总费用。
若朴素计算\(val\),复杂度为\(O(n^2k)\)。
考虑优化计算\(val(k,j)\)。
记录每个点能被覆盖的最左端点\(st\)和最右端点\(ed\)。
如果当前为\(i+1\)转移,那么从\([1,st_k-1](ed_k=i)\)的地方转移过来,\(k\)不能被覆盖到,就要加上\(k\)的补偿费用。(存在多个\(ed_k=i\),用链表储存)
考虑用线段树维护区间的\(min\{f_k+val(k,i)\}\)即可。
代码
#include <cstdio>
#include <algorithm>
const int MAXN = 20011, INF = 2000000000;
int n, k, tot, ans;
int c[MAXN], d[MAXN], s[MAXN], w[MAXN], f[MAXN], st[MAXN], ed[MAXN], ver[MAXN], next[MAXN], head[MAXN];
struct segmentTree {
int dat[MAXN << 2], lazy[MAXN << 2];
void upload(int p) {
dat[p] = std::min(dat[p << 1], dat[p << 1 | 1]);
}
void download(int p) {
if (lazy[p]) {
dat[p << 1] += lazy[p];
lazy[p << 1] += lazy[p];
dat[p << 1 | 1] += lazy[p];
lazy[p << 1 | 1] += lazy[p];
lazy[p] = 0;
}
}
void build(int p, int l, int r) {
lazy[p] = 0;
if (l == r) {
dat[p] = f[l];
return;
}
int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
upload(p);
}
int query(int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return dat[p];
download(p);
int mid = l + r >> 1;
int res = INF;
if (ql <= mid)
res = std::min(res, query(p << 1, l, mid, ql, qr));
if (qr > mid)
res = std::min(res, query(p << 1 | 1, mid + 1, r, ql, qr));
return res;
}
void modify(int p, int l, int r, int ql, int qr, int val) {
if (ql <= l && r <= qr) {
dat[p] += val;
lazy[p] += val;
return;
}
download(p);
int mid = l + r >> 1;
if (ql <= mid)
modify(p << 1, l, mid, ql, qr, val);
if (qr > mid)
modify(p << 1 | 1, mid + 1, r, ql, qr, val);
upload(p);
}
}Tree;
void add(int u, int v) {
ver[++tot] = v;
next[tot] = head[u];
head[u] = tot;
}
int main() {
scanf("%d %d", &n, &k);
for (int i = 2; i <= n; i++)
scanf("%d", &d[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &c[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &s[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
k++;
n++;
d[n] = w[n] = INF;//强制在最最后装基站统计总的答案
for (int i = 1; i <= n; i++) {
st[i] = std::lower_bound(d + 1, d + n + 1, d[i] - s[i]) - d;
ed[i] = std::lower_bound(d + 1, d + n + 1, d[i] + s[i]) - d;
if (d[i] + s[i] < d[ed[i]])
ed[i]--;
add(ed[i], i);
}
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 (int k = head[j]; k; k = next[k])
res += w[ver[k]];
}
ans = f[n];
} else {
Tree.build(1, 1, n);
for (int j = 1; j <= n; j++) {
f[j] = (j > i - 1 ? Tree.query(1, 1, n, i - 1, j - 1) : 0) + c[j];
for (int k = head[j]; k; k = next[k])
if (st[ver[k]] > 1)
Tree.modify(1, 1, n, 1, st[ver[k]] - 1, w[ver[k]]);
}
ans = std::min(ans, f[n]);
}
printf("%d", ans);
}

浙公网安备 33010602011771号