【题解】LuoGu2605:[ZJOI2010]基站选址

原题传送门
先是暴力 d p dp
d p i , j dp_{i,j} 表示第 i i 个村庄,当前建基站,总共建了 j j 个基站
d p i , j = m i n ( d p k , j 1 + w i + c o s t k , i ) ( c o s t k , i k , i ) dp_{i,j}=min(dp_{k,j-1}+w_i+cost_{k,i})(cost_{k,i}表示k,i建基站,中间无法被覆盖到的村庄需要的花费)
把枚举基站个数的循环拎到最外面,这样 d p i = m i n ( d p j + c o s t j , i ) + w i dp_{i}=min(dp_{j}+cost_{j,i})+w_i
可以用线段树把枚举 j j O ( n ) O(n) 优化成 O ( l o g n ) O(logn)
对于每个点求出 s t i , e d i st_i,ed_i 表示在这个点建立基站能覆盖到的区间
线段树维护 d p j + c o s t j , i dp_j+cost_{j,i} 的值
对于一个点 k , e d k = i k,ed_k=i ,需要在 [ 1 , s t k 1 ] [1,st_k-1] 区间内加上 w k w_k ,表示之后的点需要花费 k k 的钱

Code:

#include <bits/stdc++.h>
#define maxn 20010
#define ls rt << 1
#define rs rt << 1 | 1
using namespace std;
struct Seg{
	int l, r, sum, tag;
}seg[maxn << 2];
struct Edge{
	int to, next;
}edge[maxn << 1];
int num, head[maxn], n, k, d[maxn], s[maxn], c[maxn], w[maxn], dp[maxn], st[maxn], ed[maxn];

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void addedge(int x, int y){ edge[++num] = (Edge){y, head[x]}, head[x] = num; }

int find(int x){
	int l = 1, r = n, ans = 0;
	while (l <= r){
		int mid = (l + r) >> 1;
		if (d[mid] >= x) ans = mid, r = mid - 1; else l = mid + 1;
	}
	return ans;
}

int find2(int x){
	int l = 1, r = n, ans = 0;
	while (l <= r){
		int mid = (l + r) >> 1;
		if (d[mid] <= x) ans = mid, l = mid + 1; else r = mid - 1;
	}
	return ans;
}

void pushup(int rt){ seg[rt].sum = min(seg[ls].sum, seg[rs].sum); }

void pushdown(int rt){
	seg[ls].sum += seg[rt].tag, seg[rs].sum += seg[rt].tag;
	seg[ls].tag += seg[rt].tag, seg[rs].tag += seg[rt].tag;
	seg[rt].tag = 0;
}

void build(int rt, int l, int r){
	seg[rt].tag = 0, seg[rt].l = l, seg[rt].r = r;
	if (l == r){ seg[rt].sum = dp[l]; return; }
	int mid = (l + r) >> 1;
	build(ls, l, mid), build(rs, mid + 1, r);
	pushup(rt);
}

void update(int rt, int l, int r, int k){
	if (seg[rt].l > r || seg[rt].r < l) return;
	if (seg[rt].l >= l && seg[rt].r <= r){
		seg[rt].sum += k, seg[rt].tag += k;
		return;
	}
	pushdown(rt);
	update(ls, l, r, k), update(rs, l, r, k);
	pushup(rt);
}

int query(int rt, int l, int r){
	if (seg[rt].l > r || seg[rt].r < l) return 1e9;
	if (seg[rt].l >= l && seg[rt].r <= r) return seg[rt].sum;
	pushdown(rt);
	return min(query(ls, l, r), query(rs, l, r));
}

int main(){
	n = read(), k = read() + 1;
	for (int i = 2; i <= n; ++i) d[i] = read();
	for (int i = 1; i <= n; ++i) c[i] = read();
	for (int i = 1; i <= n; ++i) s[i] = read();
	for (int i = 1; i <= n; ++i) w[i] = read();
	d[++n] = 1e9, w[n] = 1e9;
	for (int i = 1; i <= n; ++i){
		if (d[i] - s[i] <= 0) st[i] = 1;
		else st[i] = find(d[i] - s[i]);
		if (d[i] + s[i] >= d[n]) ed[i] = n;
		else ed[i] = find2(d[i] + s[i]);
		addedge(ed[i], i);
	}
	int ans = 1e9;
	for (int j = 1; j <= k; ++j){
		if (j == 1){
			int x = 0;
			for (int i = 1; i <= n; ++i){
				dp[i] = x + c[i];
				for (int u = head[i]; u; u = edge[u].next){
					int v = edge[u].to;
					x += w[v];
				}
			}
			ans = dp[n];
		} else{
			build(1, 1, n);
			for (int i = 1; i <= n; ++i){
				dp[i] = (i >= j ? query(1, j - 1, i - 1) : 0) + c[i];
				for (int u = head[i]; u; u = edge[u].next){
					int v = edge[u].to;
					if (st[v] > 1) update(1, 1, st[v] - 1, w[v]);
				}
			}
			ans = min(ans, dp[n]);
		}
	}
	printf("%d\n", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ModestCoder_/article/details/108263517
今日推荐