P2605 [ZJOI2010] 基站选址
P2605 [ZJOI2010] 基站选址
题目描述
有 \(N\) 个村庄坐落在一条直线上,第 \(i(i>1)\) 个村庄距离第 \(1\) 个村庄的距离为 \(D_i\)。需要在这些村庄中建立不超过 \(K\) 个通讯基站,在第 \(i\) 个村庄建立基站的费用为 \(C_i\)。如果在距离第 \(i\) 个村庄不超过 \(S_i\) 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 \(i\) 个村庄没有被覆盖,则需要向他们补偿,费用为 \(W_i\)。现在的问题是,选择基站的位置,使得总费用最小。
输入格式
输入文件的第一行包含两个整数 \(N,K\),含义如上所述。
第二行包含 \(N-1\) 个整数,分别表示 \(D_2,D_3,\cdots,D_N\) ,这 \(N-1\) 个数是递增的。
第三行包含 \(N\) 个整数,表示 \(C_1,C_2,\cdots,C_N\)。
第四行包含 \(N\) 个整数,表示 \(S_1,S_2,\cdots,S_N\)。
第五行包含 \(N\) 个整数,表示 \(W_1,W_2,\cdots,W_N\)。
输出格式
输出文件中仅包含一个整数,表示最小的总费用。
数据规模与约定
\(100\%\) 的数据中,\(K\leq N\),\(K\leq 100\),\(N\leq 2\times 10^4\),\(D_i \leq 10^9\),\(C_i\leq 10^4\),\(S_i \leq10^9\),\(W_i \leq 10^4\)。
Solution:
状态设计:
设 $f_{i,j} $ 表示将第 \(i\) 个基站建立在 \(j\) 处时,前 \(j\) 个位置产生的费用 ,即 \([1,j]\) 产生的费用。
\(cost_{[l,r]}\) 表示相邻两个基站建立在 \(l,r\) 时,区间 \([l,r]\) 内的所有点产生的补偿费用 \(W_i\) 的总和。
那么我们有转移
我们发现复杂度瓶颈落在了算 \(cost\) 上,我们显然不可能枚举所有点对来计算,但是我们发现,我们也不需要知道所有的 \(cost\) 。我们只需要知道
就行了,这启示我们使用线段树优化 dp。
于是我们再预处理一些东西:\(st_i,ed_i\) 分别表示如过想让点 \(i\) 被覆盖到,那么第 \([st_i,ed_i]\) 个点中至少要有一个基站,然后对于每个 \(ed_i\) 存一下其对应的 \(i\)
dp:
我们在线段树上存 \(f_{i-1,pos}\) 的初值,然后对于每个点 \(j\) 在我们处理完 \(f_{i,j}\) 后 (将要离开 j 时) ,遍历以 \(j\) 为 \(ed_{to}\) 的所有点 \(to\) 在线段树的 $[st_{to}-1,ed_{to}] $ 区间加上一个 \(W_{to}\)。这样做的意义是:由于点 \(j\) 时能使得 to 被覆盖的最后一个基站,对于 \([j+1,n]\) 中的基站,如果离他最近的一个基站 (即第i-1个基站) 落在了 \([1,st_{to}-1]\) 范围内,那么点 \(to\) 就不能被覆盖。
这样以来,对于当前状态 \((i,j)\) ,我们成功的将
存到了线段树上,然后 \(C_j\) 又是固定的,每次转移只需要 \(O(logn)\) 的修改和查询就能完成转移。
总时间复杂度 \(O(K\times nlogn)\) ,可通过本题。
Code:
#include <bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
#define int long long
const int N=2e4+5;
const int inf=1e18;
using namespace std;
struct Tree{
int l,r,tag,w;
}t[N<<2];
long long dis[N],cost[N],range[N],pay[N];
long long f[N],st[N],ed[N],head[N];
int n,k,cnt;
struct Node{
long long to,nxt;
}e[N<<2];
void add(int u,int v)
{
e[++cnt]={v,head[u]};
head[u]=cnt;
}
void find()
{
for(int i=1;i<=n;i++)
{
st[i]=lower_bound(dis+1,dis+1+n,dis[i]-range[i])-dis;
ed[i]=lower_bound(dis+1,dis+1+n,dis[i]+range[i])-dis;
ed[i]-=(dis[ed[i]]>dis[i]+range[i]);
add(ed[i],i);
}
}
void pushup(int x)
{
t[x].w=min(t[ls].w,t[rs].w);
}
void build(int x,int l,int r)
{
t[x].tag=0;
t[x].l=l,t[x].r=r;
if(l==r)
{
t[x].w=f[l];
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(x);
}
void pushdown(int x)
{
int tag=t[x].tag;
t[ls].tag+=tag,t[rs].tag+=tag;
t[ls].w+=tag,t[rs].w+=tag;
t[x].tag=0;
}
void upd(int x,int ll,int rr,int k)
{
if(ll>rr)return ;
if(ll<=t[x].l&&t[x].r<=rr)
{
t[x].tag+=k;
t[x].w+=k;
return;
}
pushdown(x);
int mid=t[x].l+t[x].r>>1;
if(ll<=mid)upd(ls,ll,rr,k);
if(rr>mid)upd(rs,ll,rr,k);
pushup(x);
}
void query(int x,int ll,int rr,int &res)
{
if(ll>rr)return ;
if(ll<=t[x].l&&t[x].r<=rr)
{
res=min(res,t[x].w);
return;
}
int mid=t[x].l+t[x].r>>1;
pushdown(x);
if(ll<=mid)query(ls,ll,rr,res);
if(rr>mid)query(rs,ll,rr,res);
pushup(x);
}
void work()
{
int sum=0;
for(int i=1;i<=n;i++)
{
f[i]=sum+cost[i];
for(int u=head[i];u;u=e[u].nxt)
{
int to=e[u].to;
sum+=pay[to];// 能使得 to 这个点不收费的最后一个点是 i ,现在走出 i 这个点,对于后面的点,to 这个点会收费
}
}
int ans=f[n];
for(int i=2;i<=k;i++)
{
build(1,1,n);
for(int j=1;j<=n;j++)
{
int val=inf;
query(1,1,j-1,val);
//线段树上的叶子节点[pos,pos]存的是在 pos 这个点处建第 i-1 个基站时,只考虑前 pos 个点时产生的费用
f[j]=val+cost[j];
for(int u=head[j];u;u=e[u].nxt)
{
int to=e[u].to;
upd(1,1,st[to]-1,pay[to]);
}
}
ans=min(ans,f[n]);
}
printf("%lld",ans);
return;
}
#undef int
int main()
{
cin>>n>>k;
for(int i=2;i<=n;i++)
{
scanf("%lld",&dis[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&cost[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&range[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&pay[i]);
}
n++,k++;
dis[n]=pay[n]=inf;
find();
work();
return 0;
}

浙公网安备 33010602011771号