个人觉得这道题很难.......最近我要总结几篇“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;
}