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\) 的总和。

那么我们有转移

\[f_{i,j}= \min _{k \in [1,j-1]} f_{i-1,k}+cost_{k,j}+C_j \]

我们发现复杂度瓶颈落在了算 \(cost\) 上,我们显然不可能枚举所有点对来计算,但是我们发现,我们也不需要知道所有的 \(cost\) 。我们只需要知道

\[\min _{k \in [1,j-1]} f_{i-1,k}+cost_{k,j}+C_j \]

就行了,这启示我们使用线段树优化 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)\) ,我们成功的将

\[\min _{k \in [1,j-1]} f_{i-1,k}+cost_{k,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;
}

posted @ 2025-04-10 13:11  liuboom  阅读(14)  评论(0)    收藏  举报