*【ZJOI2010】基站选址 线段树优化dp难题

个人觉得这道题很难.......最近我要总结几篇“dp系列”了。

令dp[i][j]表示,在第i个位置建造第j个基站时的最小代价。

为了方便,我们令n = n + 1,K = K + 1。给最后一个点的c赋0,d和w都赋inf(0x3f3f3f3f,如果用0x7fffffff会爆int),这样的好处是最后一个站一定建造(否则inf就变成答案了,这数字也太大了),且计算进去了前n个的全部的代价。(可以脑补一下,如果做到f[n],那说明n建造了;如果没做到f[n],那就有可能少算了一段的w;新增一个节点可以帮我们简化掉大量的分类讨论)

方程:dp[i][j] = min(dp[ii][j - 1] + cost(ii + 1, i - 1)) + c[i]   ii∈[1, i)

cost的计算无疑是最大的难点。

先假设我们对于每个循环的 j (每个 j 只会调用到 j - 1,可以滚动起来,每次循环到一个新的基站数 j ,都给 j - 1的所有dp值按一定的法则建线段树。这便是线段树优化dp的思想)有这么一棵满足要求的线段树,那直接取区间最小值进行转移即可。

算cost要随着转移,向线段树中加入。(这种随操作而改变的做法是最难想到的!

在我们转移完了前i个村庄,即将处理下一个时(i+1,但此时i还是i),对线段树进行修改。

由于基站建造影响是有单调性的,即,以前刚好被i覆盖到的(即i+1就够不着了),现在不再会被覆盖了。

然后又考虑到,既然右端不被覆盖了,那么左端再不被覆盖,不就要交补偿金了吗?

左端不被补偿是什么呢?

于是求出每个节点可以被覆盖的最长的一段,记为st~ed。

如果,转移的量在st-1及以下,就够不着了。

于是给线段树上的1至st-1这段区间,全部增加一个i节点的补偿金。

这道题就解决了。

#include <cstdio>
#include <algorithm>
#define N 20010
#define inf 0x3f3f3f3f
using namespace std;

inline char gc() {
	static char now[1<<16], *S, *T;
	if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
	return *S++;
}
inline int read() {
	int x = 0; char c = gc();
	while(c < '0' || c > '9') c = gc();
	while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = gc();}
	return x;
}
void print(int x) {
	if(x > 9) print(x/10);
	putchar(x % 10 + 48);
}
struct adj {int to, next;}e[N];
int d[N], c[N], s[N], w[N], head[N], st[N], ed[N], f[N];
int n, K, cnt = 0;
inline void ins(int x, int y) {e[++cnt].to = y; e[cnt].next = head[x]; head[x] = cnt;}
int val[N<<2], tag[N<<2];
inline void update(int p) {val[p] = min(val[p<<1], val[p<<1|1]);}
inline void pushdown(int p) {
	if(tag[p]) {
		tag[p<<1]+= tag[p]; val[p<<1]+= tag[p];
		tag[p<<1|1]+= tag[p]; val[p<<1|1]+= tag[p];
		tag[p] = 0;
	}
}
void build(int p, int l, int r) {
	tag[p] = 0;
	if(l == r) {val[p] = f[l]; return ;}
	int mid = (l + r)>>1;
	build(p<<1, l, mid); build(p<<1|1, mid + 1, r);
	update(p);
}
void modify(int p, int l, int r, int x, int y, int z) {
	if(x <= l && r <= y) {tag[p]+= z; val[p]+= z; return ;}
	pushdown(p); int mid = (l + r)>>1;
	if(x <= mid) modify(p<<1, l, mid, x, y, z);
	if(mid + 1 <= y) modify(p<<1|1, mid + 1, r, x, y, z);
	update(p);
}
int query(int p, int l, int r, int x, int y) {
	if(x <= l && r <= y) return val[p];
	pushdown(p); int mid = (l + r)>>1;
	int s1 = inf, s2 = inf;
	if(x <= mid) s1 = query(p<<1, l, mid, x, y);
	if(mid + 1 <= y) s2 = query(p<<1|1, mid + 1, r, x, y);
	return min(s1, s2);
}
int main() {
	n = read(); K = read();
	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();
	++n; ++K; d[n] = w[n] = inf;
	for(int i = 1; i <= n; ++i) {
		st[i] = lower_bound(d+1, d+n+1, d[i] - s[i]) - d;
		ed[i] = lower_bound(d+1, d+n+1, d[i] + s[i]) - d;
		if(d[ed[i]] > d[i] + s[i]) --ed[i]; ins(ed[i], i);
	}
	int ans;
	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 = e[k].next) res+= w[e[k].to];
			}
			ans = f[n];
		}else {
			build(1, 1, n);
			for(int j = 1; j <= n; ++j) {
				f[j] = ((i <= j)?query(1, 1, n, i - 1, j - 1):0) + c[j];
				for(int k = head[j]; k; k = e[k].next) if(st[e[k].to] > 1) modify(1, 1, n, 1, st[e[k].to] - 1, w[e[k].to]);
			}
			ans = min(ans, f[n]);
		}
	}
	print(ans);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值