• 【洛谷2605】[ZJOI2010] 基站选址(线段树维护DP)


    点此看题面

    大致题意:(n)个村庄,每个村庄有(4)个属性:(D_i)表示与村庄(1)的距离,(C_i)表示建立基站的费用,(S_i)表示能将其覆盖的建基站范围,(W_i)表示没建设基站所要付出的代价。

    暴力(DP)

    首先我们来考虑一波暴力(DP)

    (f_{i,j})在前(i)村庄共建(j)个基站且第(i)个村庄必选所需的最小代价。

    为了方便起见,我们定义它不管其之后的代价。

    而这样统计答案又略显麻烦。

    因此我们可以考虑在最后增加一个节点((++n)即可),初始化其到村庄(1)的距离(D_i)和没建设基站所要付出的代价(W_i)(INF),且建立基站的费用(C_i)和能将其覆盖的建基站范围(S_i)(0)

    这样初始化的好处在于,选择这个村庄不会受到前面某个村庄的影响,而选择这个村庄又不会对答案造成任何影响。

    因此,这个新的节点的答案(f_{n,k+1}),就是最终答案(之所以要将(k+1),是因为选择这个新增的节点就相当于额外多选择了一个村庄建基站)。

    而就易推得转移方程:

    [f_{i,j}=C_i+min_{k=j-1}^if_{k,j-1}+GetW(k,i) ]

    其中(GetW)表示(k)(i)之间没能被覆盖、要付出额外代价的村庄的(W_i)之和。

    这应该比较显然,就相当于枚举一个节点(k)作为上个建立基站的节点,然后大力转移即可。

    (k)(j-1)开始枚举应该也是比较显然的,因为你至少要有(j-1)个节点才能建(j-1)个基站。

    然而这个式子时间复杂度差不多是(O(N^2k)),压根不可能过。

    所以就需要优化。

    考虑优化

    考虑到(i)(j)的枚举顺序其实不会对答案造成任何影响,且(i)的转移显然比(j)的转移更容易优化。

    因此,我们可以将(j)提出到最外面一层,从而转化得到如下式子:

    [f_i=C_i+min_{k=j-1}^iLastf_k+GetW(k,i) ]

    其中(Lastf_k)表示的是上一轮(DP)(f_k)的值。

    则对于这个式子,显然是要优化后面求(min)的这个过程。

    考虑一个点在什么时候不会被覆盖。

    这貌似是个智障的问题,题目中已经告诉我们,对于第(i)个基站,当距离它(S_i)范围内没有基站时,它就不会被覆盖。

    则我们就可以求出(L_x)(R_x)两个变量,分别表示最左边和最右边能覆盖到它的节点编号。(可用(lower\_bound)直接求,可惜我不知道如何处理一些细节问题,于是手写二分)

    那么,容易发现,当你到第(R_x+1)个位置时,如果上一个基站的位置(<L_x),则我们就需要将代价加上(W_x)

    于是乎就可以发现,每次代价需增加的是一段连续的区间。

    也就是说,我们需要一个算法或数据结构,能够支持区间加法和区间求和两种操作。

    显然线段树。

    线段树优化

    考虑建一棵线段树,对于每一次更新(j),初始化其为(j-1)时的(f)数组。

    然后,当我们操作完一个(i)之后,就要用所有(R_x=i)(x)去更新选择每个点的代价。

    具体操作就是在线段树上将([j-1,L_x-1])这段区间权值加(W_x)

    而你要找到所有(R_x=i)(x),邻接表即可。

    最后每次更新,就是在线段树中询问区间([j-1,i-1])中的最小值,然后加上(C_i)即可得到(f_i)

    还有,(j=1)时的答案需要单独预处理。

    具体实现可见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 20000
    #define K 100
    #define INF 1e9
    #define Gmin(x,y) (x>(y)&&(x=(y)))
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,k,ee,d[N+5],c[N+5],s[N+5],w[N+5],L[N+5],R[N+5],f[N+5],lnk[N+5];
    struct edge {int to,nxt;}e[N<<1];
    class FastIO
    {
        private:
            #define FS 100000
            #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
            #define tn (x<<3)+(x<<1)
            #define D isdigit(c=tc())
            char c,*A,*B,FI[FS];
        public:
            I FastIO() {A=B=FI;}
            Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
            Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    }F;
    class SegmentTree//线段树
    {
        private:
            #define STO l,hl,rt<<1
            #define ORZ hl+1,r,rt<<1|1
            #define PU(x) (O[x]=O[x<<1]+O[x<<1|1])
            #define PD(x) (O[x].F&&(O[x<<1]+=O[x].F,O[x<<1|1]+=O[x].F,O[x].F=0))
            int n,v[N+5];
            struct Il
            {
                int V,F;I Il (CI v=0,CI f=0):V(v),F(f){}
                I Il operator + (Con Il& t) Con {return Il(min(V,t.V));}
                I void operator += (CI x) {V+=x,F+=x;}
            }O[N<<2];
            I void Build(CI l,CI r,CI rt)
            {
                if(!(l^r)) return (void)(O[rt]=Il(v[l]));
                RI hl=l+r>>1;Build(STO),Build(ORZ),PU(rt);
            }
            I void upt(CI l,CI r,CI rt,CI ul,CI ur,CI v)//区间加法
            {
                if(ul<=l&&r<=ur) return O[rt]+=v;RI hl=l+r>>1;PD(rt);
                ul<=hl&&(upt(STO,ul,ur,v),0),ur>hl&&(upt(ORZ,ul,ur,v),0),PU(rt);
            }
            I int qry(CI l,CI r,CI rt,CI ql,CI qr)//区间求和
            {
                if(ql<=l&&r<=qr) return O[rt].V;RI hl=l+r>>1,res=INF,t;PD(rt);
                return ql<=hl&&(t=qry(STO,ql,qr),Gmin(res,t)),qr>hl&&(t=qry(ORZ,ql,qr),Gmin(res,t)),res;
            }
        public:
            I void Init(CI x,int* s) {for(RI i=1;i<=x;++i) v[i]=s[i];Build(1,n=x,1);}
            I void Update(CI l,CI r,CI v) {l<=r&&(upt(1,n,1,l,r,v),0);}
            I int Query(CI l,CI r) {return l<=r?qry(1,n,1,l,r):0;} 
            #undef STO
            #undef ORZ
    }S;
    I int GP(CI x)
    {
        RI STO=1,hl,ORZ=n;
        W(STO<=ORZ) d[hl=STO+ORZ>>1]<x?(STO=hl+1):ORZ=hl-1;
        return STO;
    }
    int main()
    {
        RI i,j,p,t,ans;for(F.read(n,k),i=2;i<=n;++i) F.read(d[i]);
        for(i=1;i<=n;++i) F.read(c[i]);for(i=1;i<=n;++i) F.read(s[i]);for(i=1;i<=n;++i) F.read(w[i]);
        for(++n,d[n]=w[n]=INF,i=1;i<=n;++i) L[i]=GP(d[i]-s[i]),R[i]=GP(d[i]+s[i]+1)-1,add(R[i],i);//初始化L[i]和R[i],然后建边
        for(t=0,i=1;i<=n;++i) for(f[i]=t+c[i],p=lnk[i];p;p=e[p].nxt) t+=w[e[p].to];ans=f[n];//预处理j=1时的答案
        for(j=2;j<=k+1;++j,Gmin(ans,f[n])) for(S.Init(n,f),i=1;i<=n;++i)//枚举状态进行转移
            for(f[i]=S.Query(j-1,i-1)+c[i],p=lnk[i];p;p=e[p].nxt) S.Update(j-1,L[e[p].to]-1,w[e[p].to]);//转移,并更新每个点的代价
        return printf("%d",ans),0;//输出答案
    }
    
  • 相关阅读:
    关于我 — About Me
    《这样说就对了》读书笔记
    LOMA280保险原理读书笔记
    .NET单元测试的艺术-3.测试代码
    .NET单元测试的艺术-2.核心技术
    .NET单元测试的艺术-1.入门
    《人人都该买保险》读书笔记
    借助 Lucene.Net 构建站内搜索引擎(下)
    借助 Lucene.Net 构建站内搜索引擎(上)
    自己动手写一个简单的MVC框架(第二版)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu2605.html
Copyright © 2020-2023  润新知