正题
luogu 2605
金牌导航 数据结构优化DP-2
题目大意
有若干个村庄在一条直线上,距离第一个村庄did_idi,在该村庄建立基站要花费cic_ici,如果在离该村不大于sis_isi的范围内有一个基站,那么该村会被信号覆盖,如果一个村庄没有被信号覆盖,那么有wiw_iwi的代价
现在最多建k个基站,问你最小代价
解题思路
设fi,jf_{i,j}fi,j为在第i个村庄建建第j个基站的费用
那么有fi,j=mink=1i−1(fk,j−1+costk,i)f_{i,j}=min_{k=1}^{i-1}(f_{k, j - 1} + cost_{k, i})fi,j=mink=1i−1(fk,j−1+costk,i),cost为在k,i建基站中间覆盖不到的村庄的代价
第二维可以在外面枚举而省掉,那么有fi=mink=1i−1(fk+costk,i)f_i=min_{k=1}^{i-1}(f_k + cost_{k, i})fi=mink=1i−1(fk+costk,i)
现在考虑计算cost的优化
可以先预处理出sti,edist_i,ed_isti,edi,分别为可以遍历到i的点中最左/右的,这可以用二分实现
计算完后在edied_iedi建立a数组,把i丢进去,得到的就是以x(i⩽x)x(i\leqslant x)x(i⩽x)为最后一个可以覆盖到的该点的点,就是x可以覆盖i,而x+1覆盖不到i
当我们计算完fif_ifi时,由于后面的点无法遍历到a里面的点,所以如果从stk(k∈a)st_k(k\in a)stk(k∈a)前面的点转移到后面,那么k点就被覆盖到,就要加上wkw_kwk,所以对[1, st_k-1]加上wkw_kwk
因为fif_ifi的上一个基站在[1,i-1]内,所以在[1,i-1]中找最小值即可
找最小值得这个过程可以用线段树来实现
对于求解答案,可以在末尾再加一个点,该点w=inf,d=inf,c=0,s=0w=inf,d=inf,c=0,s=0w=inf,d=inf,c=0,s=0,然后使建基站的最大数量+1,然后在该点找答案即可
代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 20021
using namespace std;
int n, m, ans, d[N], c[N], s[N], w[N], f[N], st[N], ed[N];
vector<int>a[N];
struct Tree
{int s[N<<2], lazy[N<<2];#define ls x*2#define rs x*2+1void up(int x){s[x] = min(s[ls], s[rs]);return;}void down(int x){if (lazy[x]){lazy[ls] += lazy[x];lazy[rs] += lazy[x];s[ls] += lazy[x];s[rs] += lazy[x];lazy[x] = 0;}return;}void build(int x, int l, int r){lazy[x] = 0;if (l == r){s[x] = f[l];return;}int mid = l + r >> 1;build(ls, l, mid);build(rs, mid + 1, r);up(x);return;}void change(int x, int L, int R, int l, int r, int y){if (l > r) return;if (L == l && R == r){lazy[x] += y;s[x] += y;return;}int mid = L + R >> 1;down(x);if (r <= mid) change(ls, L, mid, l, r, y);else if(l > mid) change(rs, mid + 1, R, l, r, y);else change(ls, L, mid, l, mid, y), change(rs, mid + 1, R, mid + 1, r, y);up(x);return;}int ask(int x, int L, int R, int l, int r){if (l > r) return 0;if (L == l && R == r) return s[x];int mid = L + R >> 1;down(x);if (r <= mid) return ask(ls, L, mid, l, r);else if (l > mid) return ask(rs, mid + 1, R, l, r);else return min(ask(ls, L, mid, l, mid), ask(rs, mid + 1, R, mid + 1, r));}
}T;
int main()
{scanf("%d%d", &n, &m);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]);n++;m++;d[n] = w[n] = 2000000001;for (int i = 1; i <= n; ++i){st[i] = lower_bound(d + 1, d + 1 + n, d[i] - s[i]) - d;//计算st,eded[i] = upper_bound(d + 1, d + 1 + n, d[i] + s[i]) - d - 1;a[ed[i]].push_back(i);}int g = 0;for (int i = 1; i <= n; ++i)//前面没有基站{f[i] = g + c[i];for (int j = 0; j < a[i].size(); ++j)g += w[a[i][j]];}ans = f[n];for (int k = 1; k <= m; ++k){T.build(1, 1, n);for (int i = 1; i <= n; ++i){f[i] = T.ask(1, 1, n, 1, i - 1) + c[i];for (int j = 0; j < a[i].size(); ++j)T.change(1, 1, n, 1, st[a[i][j]] - 1, w[a[i][j]]);//后面的点遍历不到}ans = min(ans, f[n]);}printf("%d", ans);return 0;
}