1835: [ZJOI2010]base 基站选址
Time Limit: 100 Sec Memory Limit: 64 MB
有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di。需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci。如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就成它被覆盖了。如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi。现在的问题是,选择基站的位置,使得总费用最小。 输入数据 (base.in) 输入文件的第一行包含两个整数N,K,含义如上所述。 第二行包含N-1个整数,分别表示D2,D3,…,DN ,这N-1个数是递增的。 第三行包含N个整数,表示C1,C2,…CN。 第四行包含N个整数,表示S1,S2,…,SN。 第五行包含N个整数,表示W1,W2,…,WN。
Input
输出文件中仅包含一个整数,表示最小的总费用。
Output
3 2 1 2 2 3 2 1 1 0 10 20 30
Sample Input
4
Sample Output
40%的数据中,N<=500;
100%的数据中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。
思路:
设f[i][j]表示第i个村建第j个基站的最小花费,转移方程:
f[i][j]=min{ f[k][j-1]+cost(k,i) } + c[i] ,j-1<=k<=i-1
cost(k,i)=sigma{ w[x] } k+1<=x<=i-1 , 且x未被覆盖
我们很容易发现其实j这一位是可以省去的,然而尽管如此我们还要继续降次。
方式很明确,就是维护一下min{ f[k][j-1]+cost(k,i) } ,可以用线段树维护
最外层循环j,考虑每一层i。
L[i],R[i]分别表示在i左右覆盖的范围,
假设我们已经求完了f[i][j]要求f[i+1][j],考虑那些恰可以被i覆盖到而不能被i+1覆盖到的,
即满足R[x]=i+1的点,将[1~y](R[y]=x)区间内的线段树值都加w[x],
因为前一个基站k位于[1~y](R[y]=x),也就是当f[k](1 <= k <= y,最后一个基站在k点时,无法覆盖到x),那么点x因不会被覆盖到需要做出赔偿(+w[x])。
求f[i]就是求区间[1~i-1]内线段树维护的最小值。
其中L[i],R[i]直接用二分函数求。
线段树支持区间操作区间查询。
总的时间复杂度为O(nmlogn)。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define lc u<<1
#define rc u<<1|1
using namespace std;
const int N = 20005, INF = 1e9 + 5;
int n, k, d[N], c[N], s[N], w[N];
int L[N], R[N], f[N];
int head[N], idc;
struct edge{
int to, next;
}ed[N];
void adde(int u, int v){
ed[++idc].to = v;
ed[idc].next = head[u];
head[u] = idc;
}
struct node{
int mn, flag;
}tree[N<<2];
void updata(int u){
tree[u].mn = min(tree[lc].mn, tree[rc].mn);
}
void pushdown(int u){
if(tree[u].flag){
int addd = tree[u].flag;
tree[lc].flag += addd;
tree[lc].mn += addd;
tree[rc].flag += addd;
tree[rc].mn += addd;
tree[u].flag = 0;
}
}
void build(int u, int l, int r){
tree[u].flag = 0;
if(l == r){
tree[u].mn = f[l];
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid+1, r);
updata(u);
}
void modify(int u, int l, int r, int ll, int rr, int val){
if(ll > rr) return;
if(ll<=l && r<=rr) {
tree[u].flag += val;
tree[u].mn += val;
return;
}
pushdown(u);
int mid = (l + r) >> 1;
if(ll <= mid) modify(lc, l, mid, ll, rr, val);
if(mid < rr) modify(rc, mid+1, r, ll, rr, val);
updata(u);
}
int query(int u, int l, int r, int ll, int rr){
if(ll > rr) return 0;
if(ll<=l && r<=rr) return tree[u].mn;
pushdown(u);
int mn = INF;
int mid = (l + r) >> 1;
if(ll <= mid) mn = min(mn, query(lc, l, mid, ll, rr));
if(mid < rr) mn = min(mn, query(rc, mid+1, r, ll, rr));
return mn;
}
void dp(){
int ans = INF, cc = 0;
for(int i=1; i<=n; i++){//j==1时
f[i] = cc + c[i];
for(int k=head[i]; k; k=ed[k].next)//寻找恰好被i-1覆盖,不被i覆盖的点
cc += w[ed[k].to];//之后也永远不会再覆盖了,所以可以累加
}
for(int j=2; j<=k; j++){
build(1, 1, n);//基站数目变化了,所以线段树也要重新建
for(int i=1; i<=n; i++){
f[i] = query(1,1,n,1,i-1) + c[i];//转移
for(int k=head[i]; k; k=ed[k].next){
int v = ed[k].to;
modify(1, 1, n, 1, L[v]-1, w[v]);//新增的且永远不会再覆盖的点,加上代价
}
}
ans = min(ans, f[n]);
}
printf("%d", ans);
}
int main() {
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]);
n++; k++;
d[n]=INF; w[n]=INF;
for(int i=1; i<=n; i++){
L[i] = lower_bound(d+1, d+1+n, d[i]-s[i]) - d;
R[i] = lower_bound(d+1, d+1+n, d[i]+s[i]) - d;
if(d[R[i]] - d[i] > s[i]) R[i]--;//覆盖范围是一个开区间
adde(R[i],i);//建边,为了寻找恰好被i-1覆盖,不被i覆盖的点
}
dp();
return 0;
}