zoukankan      html  css  js  c++  java
  • 【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-1leq k leq i-1) ]

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

    [dp_{i}=min{dp_{k}+pay_{k,i}}+C_i(j-1leq 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;
    }
    
  • 相关阅读:
    Shell与if相关参数
    Linux盘符漂移问题
    shell脚本,每5个字符之间插入"|",行末不插入“|”
    paste:linux合并两个文件中的列(左右合并)
    关于bc 的scale .
    RxJS与观察者模式
    什么是虚拟DOM
    JS设计模式
    JS自定义事件
    原生js实现拖拽功能
  • 原文地址:https://www.cnblogs.com/SillyTieT/p/11197907.html
Copyright © 2011-2022 走看看