原题链接:https://www.luogu.com.cn/problem/P2605
基站选址
题目描述
有 N N N 个村庄坐落在一条直线上,第 i ( i > 1 ) i(i>1) i(i>1) 个村庄距离第 1 1 1 个村庄的距离为 D i D_i Di。需要在这些村庄中建立不超过 K K K 个通讯基站,在第 i i i 个村庄建立基站的费用为 C i C_i Ci。如果在距离第 i i i 个村庄不超过 S i S_i Si 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 i i i 个村庄没有被覆盖,则需要向他们补偿,费用为 W i W_i Wi 。现在的问题是,选择基站的位置,使得总费用最小。
输入格式
输入文件的第一行包含两个整数 N , K N,K N,K,含义如上所述。
第二行包含 N − 1 N-1 N−1 个整数,分别表示 D 2 , D 3 , ⋯ , D N D_2,D_3,\cdots,D_N D2,D3,⋯,DN ,这 N − 1 N-1 N−1 个数是递增的。
第三行包含 N N N 个整数,表示 C 1 , C 2 , ⋯ , C N C_1,C_2,\cdots,C_N C1,C2,⋯,CN 。
第四行包含 N N N 个整数,表示 S 1 , S 2 , ⋯ , S N S_1,S_2,\cdots,S_N S1,S2,⋯,SN 。
第五行包含 N N N 个整数,表示 W 1 , W 2 , ⋯ , W N W_1,W_2,\cdots,W_N W1,W2,⋯,WN 。
输出格式
输出文件中仅包含一个整数,表示最小的总费用。
输入输出样例
输入 #1
3 2
1 2
2 3 2
1 1 0
10 20 30
输出 #1
4
说明/提示
40%的数据中, N ≤ 500 N \leq 500 N≤500;
100%的数据中, K ≤ N K\leq N K≤N, K ≤ 100 K\leq 100 K≤100, N ≤ 20 , 000 N\leq 20,000 N≤20,000, D i ≤ 1000000000 D_i \leq 1000000000 Di≤1000000000, C i ≤ 10000 C_i\leq 10000 Ci≤10000, S i ≤ 1000000000 S_i \leq1000000000 Si≤1000000000, W i ≤ 10000 W_i \leq 10000 Wi≤10000。
题解
可恶,我明明是来学数据结构的啊!
首先,此题一眼望去就感觉跟数据结构没什么关系,找不出到底要维护什么。反而更像个动态规划题,不如先看看朴素的动态规划该如何处理。
设 d p [ i ] [ j ] dp[i][j] dp[i][j]为只考虑前 i i i个村庄,且第 j j j个基站就建在第 i i i个村庄时的最小花费,那么转移方程就是: d p [ i ] [ j ] = m i n k ∈ [ 1 , i ) ( d p [ k ] [ j − 1 ] + ∑ p ∈ ( k , i ) 且 未 被 覆 盖 w [ p ] ) + c [ i ] dp[i][j]=min_{k\in[1,i)}(dp[k][j-1]+\sum_{p\in(k,i)且未被覆盖}w[p])+c[i] dp[i][j]=mink∈[1,i)(dp[k][j−1]+p∈(k,i)且未被覆盖∑w[p])+c[i]
这是将第 j j j座基站建在第 i i i个村庄的情况,我们需要快速求一个最小值。假若 i i i村庄不建设基站,则对于被覆盖范围为 [ k , i ] [k,i] [k,i]的村庄(对于第 p p p个村庄,它的被覆盖范围就是离第 1 1 1个村庄的距离 ∈ [ d [ p ] − s [ p ] , d [ p ] + s [ p ] ] \in[d[p]-s[p],d[p]+s[p]] ∈[d[p]−s[p],d[p]+s[p]]的村庄编号范围)来说,之后的村庄建设基站肯定不可能再覆盖到它了,若上一个基站建设在第 k k k个村庄之前,就需要补偿,后面的 d p dp dp值如果需要从转移 k k k之前的转移,则需要加上此类村庄的补偿费用,即我们需要对区间 [ 1 , k − 1 ] [1,k-1] [1,k−1]的村庄都进行一次区间加操作。
看到这里,就是一棵维护最小值并支持区间加的线段树的故事了。同时,我们需要维护每个村庄的被覆盖范围 s t [ i ] , e d [ i ] st[i],ed[i] st[i],ed[i],
建树时,初始值为 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1],之后遍历所有村庄,先按照转移方程取一次最小值更新 d p [ i ] [ j ] dp[i][j] dp[i][j],然后再找到所有 e d ed ed值 = = i ==i ==i的村庄 p p p,为区间 [ 1 , s t [ p ] − 1 ] [1,st[p]-1] [1,st[p]−1]加上 w [ i ] w[i] w[i],就能完成一轮更新。
由于每次 d p [ i ] [ j ] dp[i][j] dp[i][j]只会从 j − 1 j-1 j−1的部分求值,在建树完成后就不会再使用了,所以我们可以使用滚动数组,减少掉一维空间。
至于统计 e d ed ed值为 i i i的村庄,我使用了vector
数组,也有大佬使用了链式前向星,纯凭个人喜好。求 s t , e d st,ed st,ed数组的时候如果使用lower_bound
函数,则需要检查 e d ed ed是否满足 d [ e d [ i ] ] > d [ i ] + s [ i ] d[ed[i]]>d[i]+s[i] d[ed[i]]>d[i]+s[i],有则需要将 e d [ i ] − 1 ed[i]-1 ed[i]−1,因为lower_bound
返回的是大于等于查找值的值的下标,而我们要找的实际上是小于等于查找值的。
代码
因为 d p [ i ] dp[i] dp[i]表示在 i i i点建基站时的最优花费,且没有考虑之后的村庄,我们可以在所有村庄的最后面多加一个必定会被选中的村庄( c [ i ] = 0 c[i]=0 c[i]=0),这样直接与 d p [ n ] dp[n] dp[n]取 m i n min min就能得到答案。
#include<bits/stdc++.h>
#define ls v<<1
#define rs v<<1|1
using namespace std;
const int M=20005;
struct node{
int le,ri,mn,delta;}tree[M<<2];
int n,k,ans,d[M],c[M],s[M],w[M],st[M],ed[M],dp[M];
vector<int>bound[M];
void up(int v){
tree[v].mn=min(tree[ls].mn,tree[rs].mn);}
void build(int v,int le,int ri)
{
tree[v].le=le,tree[v].ri=ri,tree[v].delta=0;
if(le==ri){
tree[v].mn=dp[le];return;}
int mid=le+ri>>1;
build(ls,le,mid);build(rs,mid+1,ri);
up(v);
}
void push(int v,int delta){
tree[v].mn+=delta,tree[v].delta+=delta;}
void down(int v){
if(tree[v].delta)push(ls,tree[v].delta),push(rs,tree[v].delta),tree[v].delta=0;}
void add(int v,int le,int ri,int delta)
{
if(le>ri)return;
if(le<=tree[v].le&&tree[v].ri<=ri){
push(v,delta);return;}
down(v);
if(le<=tree[ls].ri)add(ls,le,ri,delta);
if(tree[rs].le<=ri)add(rs,le,ri,delta);
up(v);
}
int ask(int v,int le,int ri)
{
if(le>ri)return 0;
if(le<=tree[v].le&&tree[v].ri<=ri){
return tree[v].mn;}
int r=INT_MAX;
down(v);
if(le<=tree[ls].ri)r=ask(ls,le,ri);
if(tree[rs].le<=ri)r=min(r,ask(rs,le,ri));
return r;
}
void in()
{
scanf("%d%d",&n,&k);
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]);
}
void ac()
{
++n,++k;
d[n]=w[n]=INT_MAX;
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,
ed[i]-=d[ed[i]]>d[i]+s[i],
bound[ed[i]].push_back(i);
for(int i=1,tmp=0,j;i<=n;++i)
for(dp[i]=tmp+c[i],j=bound[i].size()-1;j>=0;--j)tmp+=w[bound[i][j]];
ans=dp[n];
for(int i=2,j,p;i<=k;++i,ans=min(ans,dp[n]))
for(build(1,1,n),j=1;j<=n;++j)
for(dp[j]=ask(1,1,j-1)+c[j],p=bound[j].size()-1;p>=0;--p)
add(1,1,st[bound[j][p]]-1,w[bound[j][p]]);
printf("%d\n",ans);
}
signed main()
{
in(),ac();
system("pause");
}