题意
在数轴的正半轴上有 N 个坐标为整数的点,第 i 个点的位置为 Di。当且仅当在 [Di−Si,Di+Si] 中存在一个被选中的点时,称点 i 被覆盖。现在可以选中 K 个点,选中第 i 个点的代价为 Ci。对于点 i,若其未被覆盖,则需要额外付出 Wi 的代价。试求代价之和的最小值。
N≤2×104,K≤min(102,N),Di,Si≤109,Ci,Wi≤104
思路
线段树优化 dp。
显然可以考虑一个朴素的 dp。如果设 f[i][j] 表示前 i 个点中选择 j 个点的最小代价和,则需要枚举最后一个被选中的点,时间复杂度不优。考虑设 f[i][j] 表示前 i 个点中,选择 j 个点且选择点 i 的最小代价和。
简单地,有状态转移方程:
f[i][j]=min[f[k][j−1]+cost(k,i),k∈[1,i−1]
其中 cost(k,i) 表示 [k+1,i−1] 中无法被覆盖的点的代价和。
暴力求 cost(k,i) 的复杂度为 O(N),总复杂度为 O(N2K),于是考虑快速求 cost(k,i)
容易发现,对于每一个点 x,必然都存在且仅存在一个连续的区间 [lx,rx],使得从该区间中任意选择一个点都可以覆盖点 x。考虑二分预处理出该区间。
不妨考虑 i 向右移动 1 后的影响。若影响转移,由于 f[k][j−1] 为定值,因此必然为 cost(k,i) 改变。对于 [k+1,i−1] 中的任意一点 p,如果 k 可以覆盖 p,那么无论 i 在 [i,n] 中取何值,p 都必然不会对 cost(k,i) 产生贡献。因此变化只有可能为 i 向右移动后,原本 [k+1,i−1] 只有 i 可以覆盖的某些点无法被覆盖。
倒推回去,如果 [k+1,i−1] 中存在只能被 i 覆盖而无法被 i+1 覆盖的点,令其为 x,则必然有 k<lx,rx=i。因此 i 向右移动时,我们只需要考虑每一个 rx=i 的点 x,然后令从 [f[1][j−1],f[lx−1][j−1]] 转移的代价增加 wx,代表此时点 x 无法被覆盖即可。
我们发现上面的操作实质上是区间加法,区间求最小值,于是考虑用线段树维护。具体地,用线段树维护 f[k][j−1]+cost(k,i) 的区间最小值。预处理出 f[1][i] 的答案后,从 j=2 开始枚举,每次建树时令 x 的权值为 f[x][j−1],然后按照上面的过程操作。
为方便地求出答案,可以考虑在 +∞ 位置虚拟一个点 x,不选中该点需要的代价为 +∞,选中该点的代价和该点可以覆盖的范围均为 0。令它处于下标 n+1,那么 min(f[n+1][k]),k∈[1,K] 即为答案。
时间复杂度 O(NKlogN)
代码
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 2e4 + 5;
const int maxk = 1e2 + 5;
const int inf = 0x3f3f3f3f;
struct node
{
int l, r, val, lazy;
} tree[maxn << 2];
int n, k;
int st[maxn], ed[maxn], f[maxn];
int d[maxn], c[maxn], s[maxn], w[maxn];
vector<int> pos[maxn];
inline int read()
{
int res = 0, flag = 1;
char ch = getchar();
while ((ch < '0') || (ch > '9'))
{
if (ch == '-') flag = -1;
ch = getchar();
}
while ((ch >= '0') && (ch <= '9'))
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
inline int min(int a, int b) { return (a <= b ? a : b); }
inline void push_up(int k) { tree[k].val = min(tree[k << 1].val, tree[k << 1 | 1].val); }
inline void push_down(int k)
{
tree[k << 1].val += tree[k].lazy;
tree[k << 1 | 1].val += tree[k].lazy;
tree[k << 1].lazy += tree[k].lazy;
tree[k << 1 | 1].lazy += tree[k].lazy;
tree[k].lazy = 0;
}
inline void build(int k, int l, int r)
{
tree[k].l = l;
tree[k].r = r;
tree[k].lazy = 0;
if (l == r)
{
tree[k].val = f[l];
return;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
push_up(k);
}
inline void update(int k, int l, int r, int w)
{
if ((tree[k].l >= l) && (tree[k].r <= r))
{
tree[k].val += w;
tree[k].lazy += w;
return;
}
push_down(k);
int mid = (tree[k].l + tree[k].r) >> 1;
if (l <= mid) update(k << 1, l, r, w);
if (r > mid) update(k << 1 | 1, l, r, w);
push_up(k);
}
inline int query(int k, int l, int r)
{
if ((tree[k].l >= l) && (tree[k].r <= r)) return tree[k].val;
push_down(k);
int mid = (tree[k].l + tree[k].r) >> 1, res = inf;
if (l <= mid) res = min(res, query(k << 1, l, r));
if (r > mid) res = min(res, query(k << 1 | 1, l, r));
return res;
}
int main()
{
int ans = inf;
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] = upper_bound(d + 1, d + n + 1, d[i] + s[i]) - d - 1;
pos[ed[i]].push_back(i);
}
int cur = 0;
for (int i = 1; i <= n; i++)
{
f[i] = c[i] + cur;
for (int p : pos[i]) cur += w[p];
}
for (int j = 2; j <= k; j++)
{
build(1, 1, n);
for (int i = 1; i <= n; i++)
{
if (i > 1) f[i] = query(1, 1, i - 1) + c[i];
for (int p : pos[i])
if (st[p] > 1) update(1, 1, st[p] - 1, w[p]);
}
ans = min(ans, f[n]);
}
printf("%d\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2021-08-09 AC 自动机
2021-08-09 manacher 算法