【数据结构优化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);
}
posted @ 2021-03-06 10:48  nymph181  阅读(66)  评论(0)    收藏  举报