洛谷2605:基站选址

题意描述:

  • 有\(N\)个村庄在一条直线上,第\(i(i>1)\)个村庄的距离第\(1\)个村庄的距离为\(D_i\)。
  • 需要在这些村庄中建立不超过\(K\)个通讯站,在第\(i\)个村庄建立基站的费用为\(C_i\)。
  • 如果在距离第\(i\)个村庄不超过\(S_i\)的范围内建立了一个通讯站,那么村庄就被基站覆盖。
  • 如果第\(i\)个村庄没有被覆盖,则要向他们补偿,费用为\(W_i\)。
  • 现在的问题是,选择基站的位置,使得总花费最小。
  • 数据范围:
    • \(k\leq N,k\leq 100,N\leq2*10^4,D_i\leq 10^9,C_i\leq 10^4,S_i\leq 10^9, w_i\leq 10^4\)。

输入格式:

  • 第一行包含两个整数\(N,K\),含义如上所述。
  • 第二行包含\(N-1\)个整数,分别表示\(D_2,D_3,...,D_n\)\((\)与第一个村庄的距离\()\),这\(N-1\)个数是递增的。
  • 第三行输入\(N\)个整数,表示\(C_1,C_2,...,C_n\)\((\)在第\(i\)个村庄建基站的费用\()\)。
  • 第四行输入\(N\)个整数,表示\(S_1,S_2,...,S_n\)\((\)第\(i\)个基站覆盖的距离\()\)。
  • 第五行输入\(N\)个整数,表示\(W_1,W_2,...,W_n\)\((\)表示补偿的费用\()\)

输出格式:

  • 输出一个整数表示最小的总费用。

思路:

  • 线段树优化\(dp\)。

  • 先考虑最朴素的\(dp\)如何处理。

  • 设\(f(i,j)\)表示表示前\(i\)个村庄建立了\(j\)个通讯站的最小开销。为了方便转移,让第\(j\)个通讯站强制建立在第\(i\)个位置。

  • 有状态转移方程:\(f(i,j)=min(f(k,j-1)+cost(k,i))+C_i\)。

  • 也可以滚动一下变成\(f(i)=min(f(k)+cost(k,i))+c_i\)。

    • 多设立一个空节点\(n+1\)。
    • 那么最后的答案就是\(ans=min(f(n+1,i))(1\leq i\leq k+1)\)。
  • 其中\(cost(k,i)\)表示从第\(k\)到第\(i\)个村庄选择外其他的都不选,所需要的补偿的费用。

    • 计算\(cost\)的时间复杂度为\(O(n)\)。
    • 计算\(dp\)的时间复杂度为\(O(nk)\),所以总时间复杂度为\(O(n^2k)\)。
  • 这个复杂度显然是无法通过的,对于枚举\(i,j\)而言,无法降低复杂度。我们可以在计算\(cost\)上下功夫。

  • 对于每一个村庄\(i\),都需要在一个范围内建基站,否则就要做出赔偿。假设第\(i\)个基站的区间是\([l_i,r_i]\)。

  • 考虑在不在\(r\)处建立基站,有如下两种可能:

    • \(1:\)不在\(r\)处建立基站,那么对于当前村庄\(i\)来说,上一个基站在\([1,l-1]\)这个区间的话,就要对\(i\)村庄进行赔偿。那么我们就需要在\([1,l-1]\)区间加上村庄\(i\)的赔偿费用。
    • \(2:\)在\(r\)处建立基站,那么就要查询区间最小值了。
  • 为了维护如上两个操作(快速区间修改,求区间最小值),我们可以采用线段树。

  • 所以转移过程中用线段树维护\(min(f(k,j)+cost(k,i))\)即可。

  • 当然还需要开两个数组辅助:

    • \(st(i)\)表示第\(i\)个村庄的左端点\(l_i\)。
    • \(ed(i)\)表示第\(i\)个村庄的右端点\(r_i\)。
  • 当然也会有很多点的\(ed\)都为\(i\),所以用前向星来存一下。

  • 时间复杂度被优化到了\(O(knlogn)\),大约在\(10^7\)左右,可以通过。

代码:

#include<bits/stdc++.h>
#define PII pair<int, int>
using namespace std;
typedef long long ll;
const int maxn = 2e4 + 10;
const ll INF = 1e18 + 10;
int n, k;
int st[maxn], ed[maxn];
ll c[maxn], w[maxn], d[maxn], s[maxn];
ll f[maxn], ans; int head[maxn], nex[maxn<<1], ver[maxn<<1], tot;
void add_edge(int x, int y){
ver[++tot] = y; nex[tot] = head[x]; head[x] = tot;
} struct SegmentTree
{
int l, r, add;
ll dat;
#define lson (p<<1)
#define rson (p<<1|1)
#define l(x) tree[x].l
#define r(x) tree[x].r
#define dat(x) tree[x].dat
#define add(x) tree[x].add
}tree[maxn<<2]; inline void pushup(int p){
dat(p) = min(dat(lson), dat(rson));
} inline void spread(int p)
{
if(add(p))
{
dat(lson) += add(p);
dat(rson) += add(p);
add(lson) += add(p);
add(rson) += add(p);
add(p) = 0;
}
} void build(int p, int l, int r)
{
add(p) = 0;
l(p) = l, r(p) = r;
if(l == r)
{
dat(p) = f[l];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(p);
} ll ask(int p, int l, int r)
{
if(l <= l(p) && r(p) <= r) return dat(p);
spread(p);
int mid = (l(p) + r(p)) >> 1;
ll val = INF;
if(l <= mid) val = min(val, ask(lson, l, r));
if(mid < r) val = min(val, ask(rson, l, r));
return val;
} void change(int p, int l, int r, int d)
{
if(l <= l(p) && r(p) <= r)
{
dat(p) += (ll)d;
add(p) += d;
return;
}
spread(p);
int mid = (l(p)+r(p)) >> 1;
if(l <= mid) change(lson, l, r, d);
if(mid < r) change(rson, l, r, d);
pushup(p);
} int main()
{
scanf("%d%d", &n, &k);
for(int i = 2; i <= n; i++) scanf("%lld", &d[i]);
for(int i = 1; i <= n; i++) scanf("%lld", &c[i]);
for(int i = 1; i <= n; i++) scanf("%lld", &s[i]);
for(int i = 1; i <= n; i++) scanf("%lld", &w[i]); d[++n] = INF, w[n] = INF; k++;
//建立虚节点 //预处理出区间
for(int i = 1; i <= n; i++)
{
st[i] = lower_bound(d+1, d+1+n, d[i]-s[i])-d;
ed[i] = lower_bound(d+1, d+1+n, d[i]+s[i])-d;
if(d[ed[i]] > d[i] + s[i]) ed[i] -= 1;
add_edge(ed[i], i);
} //开始dp
ans = INF; int t = 0;
for(int i = 1; i <= k; i++)
{
if(i == 1) //预处理出只有一个基站的答案
{
t = 0;
for(int j = 1; j <= n; j++)
{
f[j] = t + c[j];
//寻找恰好被j覆盖 但不被j+1覆盖掉的点
for(int p = head[j]; p; p = nex[p])
{
int v = ver[p];
t += w[v];
}
}
ans = f[n]; continue;
}
//f(i)每次都在变 所以每次都更新一下线段树
build(1, 1, n);
for(int j = 1; j <= n; j++)
{
if(j == 1) f[j] = c[j];
else f[j] = ask(1, 1, j-1) + c[j]; //建立基站
//不建立基站
//将赔偿金累加到线段树中去
for(int p = head[j]; p; p = nex[p])
{
int v = ver[p];
if(st[v] - 1 > 0)
change(1, 1, st[v]-1, w[v]);
}
}
//更新答案
ans = min(ans, f[n]);
}
printf("%lld\n", ans);
return 0;
}

luogu_2605: 基站选址的更多相关文章

  1. BZOJ 1835: [ZJOI2010]base 基站选址 [序列DP 线段树]

    1835: [ZJOI2010]base 基站选址 题目描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立 ...

  2. 【Bzoj 1835 基站选址】

    基站选址的区间里隐藏着DP优化的机密…… 分析:       不论是做过乘积最大还是石子合并,或者是其他的入门级别的区间DP题目的人呐,大米并认为读题后就能够轻松得出一个简洁明了的Dp转移方程.    ...

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

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

  4. 【BZOJ1835】基站选址(线段树)

    [BZOJ1835]基站选址(线段树) 题面 BZOJ 题解 考虑一个比较暴力的\(dp\) 设\(f[i][j]\)表示建了\(i\)个基站,最后一个的位置是\(j\)的最小代价 考虑如何转移\(f ...

  5. 基站选址(base.c/cpp/pas)

    基站选址(base.c/cpp/pas) 题目描述  有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费 ...

  6. 【BZOJ1835】[ZJOI2010]base 基站选址 线段树+DP

    [BZOJ1835][ZJOI2010]base 基站选址 Description 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯 ...

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

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

  8. 题解 [ZJOI2010]基站选址

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

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

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

随机推荐

  1. .Net Core3.0使用gRPC 和IdentityServer4

    gRPC是什么gRPC是可以在任何环境中运行的现代开源高性能RPC框架.它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证.它也适用于分布式计 ...

  2. iphone如何安装mitmproxy的pem文件(当iphone无法使用邮箱时)

    背景描述: 我要安装mitmproxy描述文件 mitmproxy-ca-cert.pem , 说是用iphone自带的邮箱接收然后安装即可,但悲剧的邮箱坏了[提示:无法连接服务器],查了不少方法都无 ...

  3. mosquitto: error while loading shared libraries: libwebsockets.so.12: cannot open shared object file

    错误描述: # mosquitto -c /etc/mosquitto/mosquitto.conf -dmosquitto: error while loading shared libraries ...

  4. docker离线导入导出镜像

    docker save -o mysqlies.tar 5ada6380f248(镜像id)    # 镜像id是要导出的 有镜像的机器  在有镜像的机器上执行docker load --input ...

  5. C# 转成金额每三位逗号隔开

    long aaaa = 14200666; Console.WriteLine(aaaa.ToString("N0")); Console.WriteLine(string.For ...

  6. 树莓派安装使用docker

    2019/11/11, 树莓派4B, Raspbian Buster,Docker 19.03.4 摘要:树莓派Raspbian Buster中安装Docker,Dockerfile更改软件源 安装d ...

  7. WinForm的TextBox限制只能输入数字并且屏蔽默认右键菜单

    基于Window消息实现 class TextBoxExt:TextBox { private const int WM_RBUTTONDOWN = 0x0204; private const int ...

  8. MFC中动态添加控件----寻找多年的秘籍,吐血推荐

    原文作者tianwaik 动态控件是指在需要时由Create()创建的控件,这与预先在对话框中放置的控件是不同的. 一.创建动态控件 为了对照,我们先来看一下静态控件的创建. 放置静态控件时必须先建立 ...

  9. JCEF-tab形式展示浏览器

    当我们点击target值为_blank的链接时,JCEF默认以弹出窗口的形式打开新页面,要实现tab栏形式,可参考以下步骤 1.创建一个实现CefLifeSpanHandlerAdapter的类,重写 ...

  10. 第一册: lesson 129。

    原文: Seventy miles an hour. question: What does Ann advise her husband to do next time? Look,Gary! Th ...