bzoj 1835: [ZJOI2010]基站选址
Description
有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
题解:
这题非常的有意思,完全虐翻了我,开始写的是没有优化的DP,T到40,原来正解就是这个DP的优化,原DP中,定义的是 f[i][j] 表示第j个站安放在i这个位置的最小费用,这里并没有考虑后面的基站,所以我们要新建一个点,且这个点距离很远,费用为0,这样就可以完美的合并答案到这个点上面了,然后转移就是 f[i][j]=f[k][j-1]+c[k][i] ,c[k][i]表示k-i间覆盖不到的点的w总和.
考虑优化:
难点在于求出c[k][i]这个东西,我们就考虑消掉这个东西,再思考会发现,j这一维是可以滚动的,我们就可以考虑用线段树维护f[k][j-1],然后就是维护c这个东西,我们设st[i]为i左边能覆盖到i的最远点,同理ed[i]为右边最远点,那么如果扫到了i,那么ed在i上面的点,并且如果转移是从st-1转移来的,那么这个点就覆盖不到,就要在线段树[1,st[i]-1]的位置加上w[i],这样k的位置就变成了f[k]+w[i]了,所以转移就是查询线段树[1,i-1]中的最小值
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
#define ls (node<<1)
#define rs (node<<1|1)
using namespace std;
typedef long long ll;
const int N=20005,M=105,inf=2e8;
int gi(){
int str=0;char ch=getchar();
while(ch>'9' || ch<'0')ch=getchar();
while(ch>='0' && ch<='9')str=(str<<1)+(str<<3)+ch-48,ch=getchar();
return str;
}
int n,m;int f[N],c[N],w[N],st[N],ed[N],Tree[N<<2],mark[N<<2];ll d[N],s[N];
int midl(int sta,ll x){
int l=1,r=n,mid,ret=sta;
while(l<=r){
mid=(l+r)>>1;
if(d[mid]>=x)ret=mid,r=mid-1;
else l=mid+1;
}
return ret;
}
int midr(int sta,ll x){
int l=1,r=n,mid,ret=sta;
while(l<=r){
mid=(l+r)>>1;
if(d[mid]<=x)ret=mid,l=mid+1;
else r=mid-1;
}
return ret;
}
int head[N],to[N],num=0,nxt[N];
void addedge(int x,int y){
nxt[++num]=head[x];to[num]=y;head[x]=num;
}
void upd(int node){
Tree[node]=Min(Tree[ls],Tree[rs]);
}
void build(int l,int r,int node){
mark[node]=0;
if(l==r){
Tree[node]=f[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,ls);build(mid+1,r,rs);
upd(node);
}
void pushdown(int node){
if(!mark[node])return ;
int k=mark[node];
Tree[ls]+=k;Tree[rs]+=k;
mark[ls]+=k;mark[rs]+=k;
mark[node]=0;
}
void updata(int l,int r,int node,int sa,int se,int to){
if(l>se || r<sa)return ;
if(sa<=l && r<=se){
Tree[node]+=to;mark[node]+=to;
return ;
}
pushdown(node);
int mid=(l+r)>>1;
updata(l,mid,ls,sa,se,to);updata(mid+1,r,rs,sa,se,to);
upd(node);
}
int query(int l,int r,int node,int sa,int se){
if(l>se || r<sa)return inf;
if(sa<=l && r<=se)return Tree[node];
pushdown(node);
int mid=(l+r)>>1;
int q1=query(l,mid,ls,sa,se),q2=query(mid+1,r,rs,sa,se);
upd(node);
return Min(q1,q2);
}
void work()
{
n=gi();m=gi();
for(int i=2;i<=n;i++)d[i]=gi();
for(int i=1;i<=n;i++)c[i]=gi();
for(int i=1;i<=n;i++)s[i]=gi();
for(int i=1;i<=n;i++)w[i]=gi();
n++;d[n]=2e12;
for(int i=1;i<=n;i++){
st[i]=midl(i,d[i]-s[i]);ed[i]=midr(i,d[i]+s[i]);
addedge(ed[i],i);
}
ll tot=0;
for(int i=1;i<=n;i++){
f[i]=tot+c[i];
for(int j=head[i];j;j=nxt[j]){
tot+=w[to[j]];
}
}
ll ans=f[n];
for(int j=2;j<=m+1;j++){
build(1,n,1);
for(int i=1;i<=n;i++){
if(i>1)f[i]=query(1,n,1,1,i-1)+c[i];
for(int k=head[i];k;k=nxt[k]){
int u=to[k];
if(st[u]>1)updata(1,n,1,1,st[u]-1,w[u]);
}
}
ans=Min(ans,f[n]);
}
printf("%lld\n",ans);
}
int main()
{
freopen("base.in","r",stdin);
freopen("base.out","w",stdout);
work();
return 0;
}