【BZOJ1835】基站选址(ZJOI2010)-DP+线段树优化

测试地址:基站选址
题目大意: n(20000) n ( ≤ 20000 ) 个村庄,第 i i 个村庄坐标为di。现在要在村庄中建造不超过 k(100) k ( ≤ 100 ) 个基站,在村庄 i i 建造基站的费用是ci,并且如果没有在村庄 i i 周围si距离的范围内建造基站的话,就要支付 Wi W i 的补偿费。求最小花费。
做法:本题需要用到DP+线段树优化。
首先,很容易想到区间DP的形式。令 f(i,j) f ( i , j ) 为只考虑前 i i 个村庄,在第i个村庄建造基站,在前 i i 个村庄共建造j个基站的最小花费。可以得到状态转移方程:
f(i,j)=min{f(k,j1)+cost(k,i)}+ci f ( i , j ) = min { f ( k , j − 1 ) + c o s t ( k , i ) } + c i
其中 cost(k,i) c o s t ( k , i ) 指第 k k 到第i个村庄中,如果除了第 k k 和第i个选择外都不选,所需要花费的补偿费。如果我们多设一个空点 n+1 n + 1 ,那么答案就是 min{f(n+1,x)}(1xk+1) min { f ( n + 1 , x ) } ( 1 ≤ x ≤ k + 1 )
这个方程的瓶颈在于 cost c o s t 的计算,直接暴力算肯定会炸,于是我们需要思考一个新的方法。
注意到对于每个村庄,可以覆盖它的建造基站地点是一个区间 [li,ri] [ l i , r i ] ,不难看出这个区间会对 cost(l,r)(l<li,r>ri) c o s t ( l , r ) ( l < l i , r > r i ) 做出贡献。按照DP的过程, r r 会不断递增,那么这就是一个自然的扫描线了,于是我们只需要在处理r时,把所有区间 [li,ri](ri=r1) [ l i , r i ] ( r i = r − 1 ) 的贡献累计到 cost c o s t 数组中即可,发现这是一个区间加的操作,于是用线段树维护,转移的时候在线段树中求出 f(k,j1)+cost(k,i) f ( k , j − 1 ) + c o s t ( k , i ) 的最小值即可。
于是我们就以 O(knlogn) O ( k n log ⁡ n ) 的时间复杂度完成了这一题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
int n,k,d[20010],s[20010];
int st[20010]={0},ed[20010]={0};
ll c[20010]={0},w[20010],f[20010][2]={0};
ll seg[80010],tag[80010];
int now=0,past=1;
struct interval
{
    int l,r;
    ll w;
}t[20010];

void pushdown(int no)
{
    if (tag[no]!=0)
    {
        seg[no<<1]+=tag[no],seg[no<<1|1]+=tag[no];
        tag[no<<1]+=tag[no],tag[no<<1|1]+=tag[no];
        tag[no]=0;
    }
}

void pushup(int no)
{
    seg[no]=min(seg[no<<1],seg[no<<1|1]);
}

void buildtree(int no,int l,int r)
{
    tag[no]=0;
    if (l==r)
    {
        seg[no]=f[l][past];
        return;
    }
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
    pushup(no);
}

void modify(int no,int l,int r,int s,int t,ll x)
{
    if (l>=s&&r<=t)
    {
        seg[no]+=x;
        tag[no]+=x;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no);
    if (s<=mid) modify(no<<1,l,mid,s,t,x);
    if (t>mid) modify(no<<1|1,mid+1,r,s,t,x);
    pushup(no);
}

ll query(int no,int l,int r,int s,int t)
{
    if (s>t) return 0;
    if (l>=s&&r<=t) return seg[no];
    int mid=(l+r)>>1;
    ll ans=inf;
    pushdown(no);
    if (s<=mid) ans=min(ans,query(no<<1,l,mid,s,t));
    if (t>mid) ans=min(ans,query(no<<1|1,mid+1,r,s,t));
    return ans;
}

bool cmp(interval a,interval b)
{
    return a.r<b.r;
}

int main()
{
    scanf("%d%d",&n,&k);
    d[1]=0;
    for(int i=2;i<=n;i++)
        scanf("%d",&d[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&c[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&s[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&w[i]);

    for(int i=1;i<=n;i++)
    {
        t[i].w=w[i];
        int l,r;
        l=1,r=i;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (d[mid]>=d[i]-s[i]) r=mid;
            else l=mid+1;
        }
        t[i].l=l;
        l=i,r=n;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (d[mid+1]<=d[i]+s[i]) l=mid+1;
            else r=mid;
        }
        t[i].r=l;
    }
    sort(t+1,t+n+1,cmp);
    for(int i=1;i<=n;i++)
        if (!st[t[i].r])
        {
            st[t[i].r]=i;
            if (i>1) ed[t[i-1].r]=i-1;
        }
    ed[t[n].r]=n;

    for(int i=1;i<=n;i++)
        f[i][past]=inf;
    ll ans=f[n][past];
    for(int i=1;i<=k+1;i++)
    {
        buildtree(1,0,n);
        for(int j=1;j<=n+1;j++)
        {
            if (st[j-1])
            {
                for(int p=st[j-1];p<=ed[j-1];p++)
                    modify(1,0,n,0,t[p].l-1,t[p].w);
            }
            f[j][now]=query(1,0,n,0,j-1)+c[j];
        }
        ans=min(ans,f[n+1][now]);
        swap(now,past);
    }
    printf("%lld",ans);

    return 0; 
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值