【ZJOI2010】基站选址

动态规划+线段树

神题。
ZJOI2010省选原题。
最朴素的版本:
设dp[i][j]表示在第i个村庄建设第j个基站的最小花费,pay[i][j]表示在i,j区间未被覆盖的基站所需要的罚款之和,C[i]表示在第i个村庄建设基站的花费

\[dp_{i,j}=min\{dp_{k,j-1}+pay_{k,i}\}+C_i(j-1\leq k \leq i-1) \]

复杂度O(n^2×k)
优化层次一:缩1维(看似没用的优化)
可以看到,第2位j是没有用的。
因此,状态转移方程可以变成这样(j依然要枚举):

\[dp_{i}=min\{dp_{k}+pay_{k,i}\}+C_i(j-1\leq k \leq i-1) \]

优化层次二:线段树(瞬间降成O(n^log(n)×k))
由于要在[j-1,i-1]取一个最小的k,那么可以用线段树维护一个最小值。
但是,怎么维护pay[k][i]呢。
考虑一下,设st[i]和ed[i]分别表示最左端能覆盖到i和最右端能覆盖到i的村庄编号。
那么当i变成i+1时,对于ed[k]=i的所有村庄k,若i+1在[1,st[k]-1]区间中转移时,肯定是要罚款W[k]的,因此在[1,st[k]-1]中区间加上个W[k]。
那么,我们可以将最外层循环枚举建设了j个基站,对于j=1时要特殊处理,其余的可以在build时把上一层的dp[i]值存入线段树中。
对于在[1,st[k]-1]中区间加上个W[k]可以把所有ed[k]=i的点存入一个vector中,方便修改。
这样就时间复杂度就得到了巨大蜕变。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,D[1000010],C[1000010],S[1000010],W[1000010],ans,dp[100010],st[100010],ed[100010];
vector<int> G[100010];
struct node{
    int L,R,sum,lazy;
}tree[1000010];
void Up(int p){
    tree[p].sum=min(tree[p<<1].sum,tree[p<<1|1].sum);
}
void Down(int p){
    if(tree[p].lazy==0)return;
    tree[p<<1].sum+=tree[p].lazy;
    tree[p<<1|1].sum+=tree[p].lazy;
    tree[p<<1].lazy+=tree[p].lazy;
    tree[p<<1|1].lazy+=tree[p].lazy;
    tree[p].lazy=0;
}
void build(int L,int R,int p){
    tree[p].L=L,tree[p].R=R,tree[p].lazy=0;
    int mid=(L+R)>>1;
    if(L==R){
        tree[p].sum=dp[L];
        return;
    }
    build(L,mid,p<<1);
    build(mid+1,R,p<<1|1);
    Up(p);
}
void update(int L,int R,int p,int num){
    if(tree[p].L==L&&tree[p].R==R){
        tree[p].sum+=num;
        tree[p].lazy+=num;
        return;
    }
    Down(p);
    int mid=(tree[p].L+tree[p].R)>>1;
    if(R<=mid)update(L,R,p<<1,num);
    else if(L>=mid+1)update(L,R,p<<1|1,num);
    else update(L,mid,p<<1,num),update(mid+1,R,p<<1|1,num);
    Up(p);
}
int Query(int L,int R,int p){
    //cout<<L<<" "<<R<<" "<<p<<endl;
    if(tree[p].L==L&&tree[p].R==R)return tree[p].sum;
    Down(p);
    int mid=(tree[p].L+tree[p].R)>>1;
    if(R<=mid)return Query(L,R,p<<1);
    else if(L>=mid+1)return Query(L,R,p<<1|1);
    else return min(Query(L,mid,p<<1),Query(mid+1,R,p<<1|1));
}
signed main(){
    scanf("%lld %lld",&n,&k);
    for(int i=2;i<=n;i++)scanf("%lld",&D[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&C[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&S[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&W[i]);
    n++,k++;
    D[n]=1e9+7;
    for(int i=1;i<=n;i++){
        st[i]=lower_bound(D+1,D+1+n,D[i]-S[i])-D;
        ed[i]=upper_bound(D+1,D+1+n,D[i]+S[i])-D-1;
        G[ed[i]].push_back(i);
    }
    for(int i=1;i<=k;i++){
        if(i==1){
            int res=0;
            for(int j=1;j<=n;j++){
                dp[j]=res+C[j];
                for(int k=0;k<G[j].size();k++){
                    int t=G[j][k];
                    res+=W[t];
                }
            }
            ans=dp[n];
        }
        else {
            build(1,n,1);
            for(int j=1;j<=n;j++){
                if(j<i)dp[j]=C[j];
                else dp[j]=Query(i-1,j-1,1)+C[j];
                for(int k=0;k<G[j].size();k++){
                    int t=G[j][k];
                    if(st[t]-1>=1)update(1,st[t]-1,1,W[t]);
                }
            }
            ans=min(ans,dp[n]);
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2019-07-16 22:17  TieT  阅读(311)  评论(0编辑  收藏  举报