[BZOJ1835][ZJOI2010]base 基站选址
题目描述
有\(N\)个村庄坐落在一条直线上,第\(i(i>1)\)个村庄距离第\(1\)个村庄的距离为\(D_i\)。
需要在这些村庄中建立不超过\(K\)个通讯基站,在第\(i\)个村庄建立基站的费用为\(C_i\)。
如果在距离第\(i\)个村庄不超过\(S_i\)的范围内建立了一个通讯基站,那么就成它被覆盖了。
如果第\(i\)个村庄没有被覆盖,则需要向他们补偿,费用为\(W_i\)。
现在的问题是,选择基站的位置,使得总费用最小。
40%的数据中,\(N<=500\);
100%的数据中,\(K<=N,K<=100,N<=20000,D_i<=1e9,C_i<=1e4,S_i<=1e9\);
Input
输入第一行包含两个整数\(N,K\),含义如上所述。
第二行包含\(N−1\)个整数,分别表示\(D_2,D_3,…,D_N\) ,这\(N−1\)个数是递增的。
第三行包含\(N\)个整数,表示\(C_1,C_2,…,C_N\)。
第四行包含\(N\)个整数,表示\(S_1,S_2,…,S_N\)。
第五行包含\(N\)个整数,表示\(W_1,W_2,…,W_N\)。
Output
一个整数,表示最小的总费用
Sample Input
3 2
1 2
2 3 2
1 1 0
10 20 30
Sample Output
4
可以很方便的看出朴素的\(dp\);
\(dp[i][j]=dp[k][j-1]+ctk[k][i]+cost[i],0<j<i\)
其中,\(ctk[i][j]\)表示当时的情况下\(i-j\)的补偿总和。
\(O(n)\)统计\(ctk[i][j]\),总时间复杂度为\(O(n^2*k)\)
在利用刷表法,可以在空间上利用滚动数组消掉一维。
同时,利用线段树维护区间最小值降掉一个时间复杂度。
此时,总时间复杂度都堆积到统计\(ctk[i][j]\)上。
应该如何快速的统计\(ctk[i][j]\)呢?
我们先处理出,一个村庄能被覆盖的左右端点。
在一个村庄的右端点上储存该村庄的编号。
先利用前缀和统计出\(dp\)数组的初值。
考虑一个点的\(dp\)初值,在该点往右不能覆盖到的端点,自然在下一个点上也无法覆盖到,直接累加不能覆盖到的点的补偿值即可。
决策单调性
每次,只用建线段树,利用区间最值去更新即可。
代码如下
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,e) for(reg int i=H[e]; i; i=G[i].nxt)
inline int Read() {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a,T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
return a<b?a=b,1:0;
}
const int N=2e4+5,M=2e6+5;
int n,m,dis[N],cost[N],W[N],S[N],dp[N],L[N],R[N];
struct SetmentTree {
int Mi[N<<2],lazy[N<<2];
void up(int x) {
Mi[x]=min(Mi[x<<1],Mi[x<<1|1]);
}
void down(int x) {
if(!lazy[x])return;
Mi[x<<1]+=lazy[x];
Mi[x<<1|1]+=lazy[x];
lazy[x<<1]+=lazy[x];
lazy[x<<1|1]+=lazy[x];
lazy[x]=0;
}
void build(int L,int R,int num) {
lazy[num]=0;
if(L==R) {
Mi[num]=dp[L];
return;
}
int mid=(L+R)>>1;
build(L,mid,num<<1);
build(mid+1,R,num<<1|1);
up(num);
}
void update(int L,int R,int l,int r,int num,int x){
if(l>r)return;
if(l==L&&r==R){
lazy[num]+=x;
Mi[num]+=x;
return;
}
down(num);
int mid=(L+R)>>1;
if(r<=mid)update(L,mid,l,r,num<<1,x);
else if(l>mid)update(mid+1,R,l,r,num<<1|1,x);
else {
update(L,mid,l,mid,num<<1,x);
update(mid+1,R,mid+1,r,num<<1|1,x);
}
up(num);
}
int query(int L,int R,int l,int r,int num){
if(l>r)return 0;
if(l==L&&r==R)return Mi[num];
down(num);
int mid=(L+R)>>1;
if(r<=mid)return query(L,mid,l,r,num<<1);
else if(l>mid)return query(mid+1,R,l,r,num<<1|1);
else return min(query(L,mid,l,mid,num<<1),query(mid+1,R,mid+1,r,num<<1|1));
}
} Tree;
vector<int>edge[N];
signed main() {
n=Read(),m=Read();
rep(i,2,n)dis[i]=Read();
rep(i,1,n)cost[i]=Read();
rep(i,1,n)S[i]=Read();
rep(i,1,n)W[i]=Read();
n++,dis[n]=S[n]=W[n]=1e9;//设立监视哨
rep(i,1,n){
L[i]=lower_bound(dis+1,dis+n+1,dis[i]-S[i])-dis;//左端点
R[i]=upper_bound(dis+1,dis+n+1,dis[i]+S[i])-dis-1;//右端点
edge[R[i]].push_back(i);//存点
}
int Sum=0;
rep(i,1,n){
dp[i]=Sum+cost[i];
ret(j,0,edge[i].size())Sum+=W[edge[i][j]];//统计补偿和
}
int Ans=dp[n];
rep(i,1,m){
Tree.build(1,n,1);
rep(j,1,n){
dp[j]=Tree.query(1,n,1,j-1,1)+cost[j];
ret(k,0,edge[j].size()){
int y=edge[j][k];
Tree.update(1,n,1,L[y]-1,1,W[y]);
}
}
Min(Ans,dp[n]);
}
printf("%d",Ans);
}