前言

题目链接:洛谷

题意简述

[ZJOI2010] 基站选址。

有 \(N\) 个村庄坐落在一条直线上,第 \(i\) 个村庄距离第 \(1\) 个村庄的距离为 \(D_i\)。需要在这些村庄中建立不超过 \(K\) 个通讯基站,在第 \(i\) 个村庄建立基站的费用为 \(C_i\)。如果在距离第 \(i\) 个村庄不超过 \(S_i\) 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 \(i\) 个村庄没有被覆盖,则需要向他们补偿,费用为 \(W_i\)。请选择基站的位置,使得总费用最小。

题目分析

显然考虑使用 DP。状态也很好想,用 \(f[i][j]\) 表示考虑了前 \(i\) 个村庄,在第 \(i\) 个村庄上有一个基站,一共建了 \(j\) 个基站,前 \(i\) 个村庄的花费。

思考这个状态为什么是对的。也即为什么记录前 \(i\) 个村庄的花费是正确的。发现,如果第 \(i\) 个位置建了基站,前面的村庄都覆盖不到的话,再在后面建基站,也是不会覆盖得到的。这样就没有了后效性,这个状态是正确的。

很容易得到转移方程:

\[f[i][j] = \min _ {k = 0} ^ {i - 1} \Big \lbrace f[k][j - 1] + cost[k][i] + C_i \Big \rbrace
\]

其中,\(cost[l][r]\) 表示如果在 \(l\) 和 \(r\) 处建有基站,在 \(l \sim r\) 中,不能被覆盖到的村庄的赔偿总和。用形式化的语言表示如下,在条件中已经暗含了 \(i \in [l, r]\) 这个限制:

\[cost[l][r] = \sum _ {D_l < D_i - S_i \wedge D_i + S_i < D_r} W_i
\]

二维数点问题吧。考虑再简化一下,记 \(L[i]\) 表示 \(i\) 左边最远能覆盖到 \(i\) 的基站。用形式化的语言表示如下:

\[L[i] = \min _ {D_j \geq D_i - S_i} \Big \lbrace j \Big \rbrace
\]

同理记 \(R[i]\) 表示 \(i\) 右边最远能覆盖到 \(i\) 的基站。

\[R[i] = \max _ {D_j \leq D_i + S_i} \Big \lbrace j \Big \rbrace
\]

这个可以用二分预处理。那么 \(cost[l][r]\) 的表达式变为了:

\[cost[l][r] = \sum _ {l < L[i] \wedge R[i] < r} W_i
\]

考虑 \(r\) 从小到大的扫描线,维护一颗线段树,记录在当前右端点为 \(r\) 的所有 \(cost[l][r]\) 的值。查询完后,对所有 \(R[i] = r\) 的 \(i\),对 \(0 \sim L[i] - 1\) 增加 \(W_i\)。可以画出坐标系理解一下。

我们显然不能预处理出所有的 \(cost[l][r]\),所以只能边 DP 边查询。不妨把最外层的状态去掉,记 \(g[i] = f[i][j - 1]\),那么重写转移方程:

\[\begin{aligned}
f[i] &= \min _ {k = 0} ^ {i - 1} \Big \lbrace g[k] + cost[k][i] + C_i \Big \rbrace \\
&= \min _ {k = 0} ^ {i - 1} \Big \lbrace g[k] + cost[k][i] \Big \rbrace + C_i
\end{aligned}
\]

所以用来扫描线的线段树,建树时把 \(g[k]\) 作为初值,这样每次只用查询线段树里 \(0 \sim i - 1\) 的最小值就行了。

初始 \(f[0] = 0\),其它 \(f[i] = \infty\)。答案 \(ans = \min \limits _ {k = 0} ^ {n} \Big \lbrace g[k] + cost[k][n + 1] \Big \rbrace\),也即线段树查询 \(0 \sim n\) 的最小值。

时间复杂是:\(\Theta(nk \log n)\)。

代码

略去快读快写。

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main() { return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std; #include <algorithm>
#include <vector> int n, k, ans = 0x3f3f3f3f;
int d[20010], c[20010], r[20010], w[20010];
int L[20010], R[20010], f[20010];
vector<pair<int, int>> yzh[20010]; struct Segment_Tree {
#define lson (idx << 1 )
#define rson (idx << 1 | 1) struct node {
int l, r;
int mi, lazy;
} tree[20010 << 2]; void pushup(int idx) {
tree[idx].mi = min(tree[lson].mi, tree[rson].mi);
} void build(int idx, int l, int r) {
tree[idx] = {l, r, 0x3f3f3f3f, 0};
if (l == r) return tree[idx].mi = f[l], void();
int mid = (l + r) >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
pushup(idx);
} void pushtag(int idx, int val) {
tree[idx].lazy += val;
tree[idx].mi += val;
} void pushdown(int idx) {
if (!tree[idx].lazy) return;
pushtag(lson, tree[idx].lazy);
pushtag(rson, tree[idx].lazy);
tree[idx].lazy = 0;
} void modify(int idx, int l, int r, int val) {
if (tree[idx].l > r || tree[idx].r < l) return;
if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, val);
pushdown(idx);
modify(lson, l, r, val);
modify(rson, l, r, val);
pushup(idx);
} int query(int idx, int l, int r) {
if (tree[idx].l > r || tree[idx].r < l) return 0x3f3f3f3f;
if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].mi;
pushdown(idx);
return min(query(lson, l, r), query(rson, l, r));
} void output(int idx) {
if (tree[idx].l == tree[idx].r) {
cout << tree[idx].mi << " \n"[tree[idx].l == n] << flush;
return;
}
pushdown(idx);
output(lson);
output(rson);
} #undef lson
#undef rson
} tree; signed main() {
fread(buf, 1, MAX, stdin);
read(n), read(k), d[0] = -0x3f3f3f3f;
for (int i = 2; i <= n; ++i) read(d[i]);
for (int i = 1; i <= n; ++i) read(c[i]);
for (int i = 1; i <= n; ++i) read(r[i]);
for (int i = 1; i <= n; ++i) read(w[i]);
for (int i = 1; i <= n; ++i) {
L[i] = lower_bound(d, d + n + 1, d[i] - r[i]) - d;
R[i] = upper_bound(d, d + n + 1, d[i] + r[i]) - d - 1;
yzh[R[i]].push_back({L[i], w[i]});
f[i] = 0x3f3f3f3f;
}
for (int i = 0; i <= k; ++i) {
tree.build(1, 0, n);
for (int j = 1; j <= n; ++j) {
f[j] = tree.query(1, 0, j - 1) + c[j];
for (const auto& [l, w]: yzh[j])
tree.modify(1, 0, l - 1, w);
}
ans = min(ans, tree.query(1, 0, n));
}
printf("%d", ans);
return 0;
}

后记 & 反思

据说有 \(\Theta(n \log n \log w)\) 的做法?但是我太蒻了,并不会。

模拟赛的时候没想到 DP 的时候只用考虑前 \(i\) 个村庄的花费,而是用总的减去被覆盖到的,虽说本质相同,但是难分析。

挺板子的,但是没做出来。可能是时间不够了?

[ZJOI2010] 基站选址 题解的更多相关文章

  1. 题解 [ZJOI2010]基站选址

    题解 [ZJOI2010]基站选址 题面 解析 首先考虑一个暴力的DP, 设\(f[i][k]\)表示第\(k\)个基站设在第\(i\)个村庄,且不考虑后面的村庄的最小费用. 那么有\(f[i][k] ...

  2. 【题解】Luogu P2605 [ZJOI2010]基站选址

    原题传送门:P2604 [ZJOI2010]基站选址 看一眼题目,变知道这题一定是dp 设f[i][j]表示在第i个村庄修建第j个基站且不考虑i+1~n个村庄的最小费用 可以得出f[i][j] = M ...

  3. 【LG2605】[ZJOI2010]基站选址

    [LG2605][ZJOI2010]基站选址 题面 洛谷 题解 先考虑一下暴力怎么写,设\(f_{i,j}\)表示当前\(dp\)到\(i\),且强制选\(i\),目前共放置\(j\)个的方案数. 那 ...

  4. luogu P2605 [ZJOI2010]基站选址 线段树优化dp

    LINK:基站选址 md气死我了l达成1结果一直调 显然一个点只建立一个基站 然后可以从左到右进行dp. \(f_{i,j}\)表示强制在i处建立第j个基站的最小值. 暴力枚举转移 复杂度\(n\cd ...

  5. [ZJOI2010]基站选址,线段树优化DP

    G. base 基站选址 内存限制:128 MiB 时间限制:2000 ms 标准输入输出 题目类型:传统 评测方式:文本比较   题目描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离 ...

  6. 题解 P2605 【[ZJOI2010]基站选址】(From luoguBlog)

    线段树优化dp 数组f[i][j]表示在前i个村庄内,第j个基站建在i处的最小费用 根据交线牛逼法和王鹤松式可得方程 f[i][j]=min(f[k][j−1]+cost(k,i)) cost(k,i ...

  7. bzoj 1835: [ZJOI2010]基站选址

    Description 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci.如果在距离第i个村庄 ...

  8. [ZJOI2010]基站选址

    题目描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci.如果在距离第i个村庄不超过Si的范 ...

  9. BZOJ1835 [ZJOI2010] 基站选址 【动态规划】【线段树】

    题目分析: 首先想一个DP方程,令f[m][n]表示当前在前n个村庄选了m个基站,且第m个基站放在n处的最小值,转移可以枚举上一个放基站的村庄,然后计算两个村庄之间的代价. 仔细思考两个基站之间村庄的 ...

  10. zjoi2010基站选址

    线段树优化dp 题解: 首先dp挺简单的 f[i,k]=f[j,k-1]+solve(i+1,j-1) 然后这个是可以n^2*k搞得 然后考虑这个solve(i+1,j-1) 当i延伸了一个位置的时候 ...

随机推荐

  1. 什么是Web3.0,和区块链又有什么关系?

    又是一个莫名其妙的词语的新起-.- 我大致归纳为以下几个点 什么是web3.0 Web 3.0(Web3)是下一代互联网的发展方向,旨在创建一个更加去中心化.用户控制和数据保护的网络环境.与之前的We ...

  2. .NET使用原生方法实现文件压缩和解压

    前言 在.NET中实现文件或文件目录压缩和解压可以通过多种方式来完成,包括使用原生方法(System.IO.Compression命名空间中的类)和第三方库(如:SharpZipLib.SharpCo ...

  3. redhat6.5 升级 openssl

    上传版本包: openssl-1.0.1u.tar.gz 解压缩安装包: tar -xf openssl-1.0.1u.tar.gz 安装版本: ./config --prefix=/usr/loca ...

  4. 利用Vue做一个小购物车

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  5. 嵌入式编程中使用qemu能够做什么?

    嵌入式编程中使用qemu能够做什么? 背景 学习QEMU以后,为了拓展视野,才发现QEMU可以做这么多的事情. 原文链接(有删改):https://www.eet-china.com/mp/a5634 ...

  6. 写给rust初学者的教程(一):枚举、特征、实现、模式匹配

    这系列RUST教程一共三篇.这是第一篇,介绍RUST语言的入门概念,主要有enum\trait\impl\match等语言层面的东西. 安装好你的rust开发环境,用cargo创建一个空项目,咱们直接 ...

  7. 【C++】使用ort推理yolov10

    [C++]使用ort推理yolov10 前言:由于笔者是编导专业,想玩玩yolo模型,搜来搜去全是python,所以在学会之后写一篇文章帮助和笔者同样情况的人 环境 Windows 10 C++17 ...

  8. Pandas库学习笔记(6) -- Pandas 基本方法

    Pandas 基本方法实例 到目前为止,我们了解了三个Pandas DataStructures以及如何创建它们.由于它在实时数据处理中的重要性,因此我们将主要关注DataFrame对象,并讨论其他一 ...

  9. 结构体_C

    // Code file created by C Code Develop #include "ccd.h" #include "stdio.h" #incl ...

  10. 配置docker镜像时碰到的问题

    在配置docker镜像时,首先我是这样操作的 , echo "registry-mirrors": ["https://ry1ei7ga.mirror.aliyuncs. ...