问题描述

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

输入格式

输入文件的第一行包含两个整数N,K,含义如上所述。

第二行包含N-1个整数,分别表示D2,D3,…,DN ,这N-1个数是递增的。

第三行包含N个整数,表示C1,C2,…CN。

第四行包含N个整数,表示S1,S2,…,SN。

第五行包含N个整数,表示W1,W2,…,WN。

输出格式

输出文件中仅包含一个整数,表示最小的总费用。

样例输入

3 2

1 2

2 3 2

1 1 0

10 20 30

样例输出

4

说明

40%的数据中,N<=500;

100%的数据中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。

解析

可以想到是动态规划的题目。设\(f[i][j]\)表示把第j个基站修在第i个村庄且不考虑第i+1到第n个村庄的最小费用。那么,状态转移方程为

\[f[i][j]=min(f[k][j-1]+cost(k,i))
\]

其中\(cost(k,i)\)表示\(k\)到\(i\)的村庄中没有被\(i\)和\(k\)基站覆盖到的村庄所需的赔偿费用。这样做是可能会达到\(O(n^2k)\)的复杂度。考虑如何优化。

对每一个村庄\(i\)求出最左边能够覆盖到它的村庄和最右边能够覆盖到它的村庄(记为\(l[i]\)和\(r[i]\))。那么在\(l[i]\)之前的村庄都无法覆盖到\(i\)。在往\(i+1\)转移的时候,对于村庄\(k\)满足\(r[k]=i\),如果是从\([1,l[k]-1]\)转移过来的话,一定会赔偿\(k\),即\([1,l[k]-1]\)的费用都会增加\(k\)的赔偿。所以,用线段树维护\(f[k][j-1]+cost(k,i)\)的值,同时用邻接表记录\(r[k]=i\)的\(k\),每次转移都区间修改,然后在\([1,i-1]\)中取最小值。

还有一点,由于DP时没有考虑后面的村庄,为了避免漏掉最后一个村庄的情况,我们可以加入第\(n+1\)个村庄,并强制这个村庄上建立第\(k+1\)个基站。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 20002
using namespace std;
const int inf=1<<30;
struct SegmentTree{
int dat,add;
}t[N*4];
int head[N],ver[N*2],nxt[N*2],tot;
int n,k,i,j,x,d[N],c[N],s[N],w[N],l[N],r[N],f[N];
int read()
{
char c=getchar();
int w=0;
while(c<'0'||c>'9') c=getchar();
while(c<='9'&&c>='0'){
w=w*10+c-'0';
c=getchar();
}
return w;
}
void insert(int x,int y)
{
tot++;
ver[tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void build(int p,int l,int r)
{
t[p].add=t[p].dat=0;
if(l==r){
t[p].dat=f[l];
return;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].dat=min(t[p*2].dat,t[p*2+1].dat);
}
void spread(int p)
{
if(t[p].add){
t[p*2].dat+=t[p].add;
t[p*2+1].dat+=t[p].add;
t[p*2].add+=t[p].add;
t[p*2+1].add+=t[p].add;
t[p].add=0;
}
}
void change(int p,int l,int r,int ql,int qr,int x)
{
if(ql>qr) return;
if(ql<=l&&r<=qr){
t[p].dat+=x;
t[p].add+=x;
return;
}
int mid=(l+r)/2;
spread(p);
if(ql<=mid) change(p*2,l,mid,ql,qr,x);
if(qr>mid) change(p*2+1,mid+1,r,ql,qr,x);
t[p].dat=min(t[p*2].dat,t[p*2+1].dat);
}
int ask(int p,int l,int r,int ql,int qr)
{
if(ql>qr) return 0;
if(ql<=l&&r<=qr) return t[p].dat;
int mid=(l+r)/2,ans=1<<30;
spread(p);
if(ql<=mid) ans=min(ans,ask(p*2,l,mid,ql,qr));
if(qr>mid) ans=min(ans,ask(p*2+1,mid+1,r,ql,qr));
return ans;
}
int main()
{
n=read(),k=read();
for(i=2;i<=n;i++) d[i]=read();
for(i=1;i<=n;i++) c[i]=read();
for(i=1;i<=n;i++) s[i]=read();
for(i=1;i<=n;i++) w[i]=read();
n++;k++;
d[n]=w[n]=inf;
for(i=1;i<=n;i++){
l[i]=lower_bound(d+1,d+n+1,d[i]-s[i])-d;
r[i]=lower_bound(d+1,d+n+1,d[i]+s[i])-d;
if(d[r[i]]>d[i]+s[i]) r[i]--;
insert(r[i],i);
}
int ans=inf;
for(j=1;j<=k;j++){
if(j==1){
int tmp=0;
for(i=1;i<=n;i++){
f[i]=tmp+c[i];
for(x=head[i];x;x=nxt[x]) tmp+=w[ver[x]];
}
}
else{
build(1,1,n);
for(i=1;i<=n;i++){
f[i]=ask(1,1,n,j-1,i-1)+c[i];
for(x=head[i];x;x=nxt[x]) change(1,1,n,1,l[ver[x]]-1,w[ver[x]]);
}
}
ans=min(ans,f[n]);
}
printf("%d\n",ans);
}

[洛谷P2605] ZJOI2016 基站选址的更多相关文章

  1. 洛谷$P2605\ [ZJOI2010]$基站选址 线段树优化$dp$

    正解:线段树优化$dp$ 解题报告: 传送门$QwQ$ 难受阿,,,本来想做考试题的,我还造了个精妙无比的题面,然后今天讲$dp$的时候被讲到了$kk$ 先考虑暴力$dp$?就设$f_{i,j}$表示 ...

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

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

  3. 洛谷P3348 [ZJOI2016]大森林(LCT,虚点,树上差分)

    洛谷题目传送门 思路分析 最简单粗暴的想法,肯定是大力LCT,每个树都来一遍link之类的操作啦(T飞就不说了) 考虑如何优化算法.如果没有1操作,肯定每个树都长一样.有了1操作,就来仔细分析一下对不 ...

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

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

  5. 洛谷P2605 基站选址

    神TM毒瘤线段树优化DP......新姿势get. 题意:有n个村庄,在里面选不多于k个建立基站. 建立基站要ci的费用.如果一个村庄方圆si内没有基站,那么又要支出wi的费用.求最小费用. 解:很显 ...

  6. 洛谷 P3349 [ZJOI2016]小星星 解题报告

    P3349 [ZJOI2016]小星星 题目描述 小\(Y\)是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品.她有\(n\)颗小星星,用\(m\)条彩色的细线串了起来,每条细线连着两颗小星星. 有一 ...

  7. P2605 [ZJOI2010]基站选址

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

  8. luogu P2605 [ZJOI2010]基站选址

    luogu 先考虑朴素dp,设\(f_{i,j}\)表示在第\(i\)个村庄放了基站,一共放了\(j\)次,且只考虑前面村庄影响的答案.这里可以把\(j\)放在外面枚举,然后从\(f_{k,j-1}( ...

  9. ●洛谷P3348 [ZJOI2016]大森林

    题链: https://www.luogu.org/problemnew/show/P3348 题解: LCT,神题 首先有这么一个结论: 每次的1操作(改变生长点操作),一定只会会对连续的一段区间产 ...

随机推荐

  1. python学习笔记四 (运算符重载和命名空间、类)

    从以上代码中应该了解到: obj.attribute  查找的顺序: 从对象,类组成的树中,从下到上,从左到右到查找最近到attribute属性值,因为rec中存在name的属性,所以x.name可以 ...

  2. Redis数据库连接

    1.建立maven项目pox.xml导入依赖包 <dependency> <groupId>io.lettuce</groupId> <artifactId& ...

  3. nigx下配置tp5.1路由

    打开宝塔面板,找到你要配置路由的网站并找到配置文件(如图1) (图1) 2.在配置文件里添加一下代码 set $root = /www/wwwroot/www.blogs.test/public; # ...

  4. 使用Python基于OpenCV和Tesseract的OCR

    OCR OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗.亮的模式确定其形状,然后用字符识别方法将形 ...

  5. 用纯 CSS 创作一个在容器中反弹的小球

    效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/jKVbyE 可交互视频教 ...

  6. Codeforces 1194A. Remove a Progression

    传送门 再一次题目看错浪费一小时...退役算了 自己手玩一下发现划掉的都是奇数,最后所有奇数都划掉了,证明也挺显然的 所以直接输出 $2m$ 即可 #include<iostream> # ...

  7. 剑指offer-数字在排序数组中出现的次数-数组-python

    题目描述 统计一个数字在排序数组中出现的次数.   python 内置函数 count()一行就能搞定   解题思路 二分查找到给定的数字及其坐标.以该坐标为中点,向前向后找到这个数字的 始 – 终 ...

  8. ELK视频下载

    Elasticsearch , Logstash, Kibana 相关视频下载地址:Beats.Elastic Stack.ElasticSearch.Kibana.Logstash下载地址:链接:h ...

  9. spark精华面试题

    1.driver的功能是什么? 1)一个Spark作业运行时包括一个Driver进程,也是作业的主进程,具有main函数,并且有SparkContext的实例,是程序的人口点: 2)功能:负责向集群申 ...

  10. 网速监控-nload

    用来监控系统网卡实时网速的. 安装 yum install nload -y # 或 apt install nload -y 使用 # 直接运行默认监控第一个网卡, 使用上下方向键来切换网卡. nl ...