基站选址的区间里隐藏着DP优化的机密……

分析:

      不论是做过乘积最大还是石子合并,或者是其他的入门级别的区间DP题目的人呐,大米并认为读题后就能够轻松得出一个简洁明了的Dp转移方程。

      由于这道题每个村庄i仅有两种状态:①自己有一个基站②自己不是基站,但是自己的范围S[i]里有基站。基于这样的关系,可以得出一个容易理解的Dp转移方程:

      [设f[k][i]表示1~i的村庄中选取k个村庄安放基站,并且第k个村庄就安放在村庄i,使得所有村庄合法的最小花费]

        f[k][i]=min(f[k-1][j]+Cost(j,i))+c[i] (j<i)

     这个状态转移表达的含义是,村庄i,j安放了基站。据此Cost(i,j)表示的则是在村庄i,j之间没能接收到基站信号的基站的额外费用w[i]之和(即表示由于不能用基站处理掉而付出的额外代价)。

      到此为止这个解法已经成功了一半。不成功的地方是时间复杂度在本提数据范围下是不能承受的——O(n2k)。

      因此我们考虑Dp的优化。让我们幻想一下,如果能够存下f[k-1][]+Cost们的最优值,那么就不需要对于每个i转移花n次来枚举来源了!随后可以发现,难点在于Cost(i,j)的快速计算。

      仔细分析Cost(i,j)的定义,我们需要找到一种方法,能够快速求出在两个基站i,j之间有哪些村庄无法被覆盖(调皮的是,这里覆盖范围是按各个村庄来定的)。由于f[k][i]表示合法方案,所以在状态转移的时候,我们要注意对于[1,j]之间的村庄已经处理好,我们只要考虑(j,i)中村庄是否覆盖的问题。那么如果一个村庄无法被j,i两个村庄覆盖,它的信号接收范围长啥样呢?

     就是这样:左手摸不着j,右手碰不到i。然后我们尝试利用范围这一特性,我们发现,如果(j,i)这一对组合,x覆盖不到,那么对于所有(j,I)(I大于i)都覆盖不到x。所以我们想到,一种(j,i)可以为后来的(j,I)提供一些小小信息。同理地,如果(j,i)这一对组合,x覆盖不到,那么对于所有(J,i)(J小于j)都覆盖不到x。总结来说啦,就是一种单调性:

     结论:如果状态转移中(j,i)情况下(表示在i,j放置基站),如果之间某个村庄x无法被覆盖,那么对于所有状态转移中(J,I)[J<j&&I>i]都无法覆盖x(毕竟越来越远了嘛)。

     为了方便,我们动用三个数组参与Dp的优化行动:

     ·int left_cur[i]:表示在村庄i的范围内[-s[i],+s[i]],最靠左边的那个村庄的位置(也就是下标最小的点,放置基站依旧可以覆盖i);

     ·int left_cur[i]:表示在村庄i的范围内[-s[i],+s[i]],最靠右边的那个村庄的位置(也就是下标最大的点,放置基站依旧可以覆盖i);

     ·vector<int> cur_right[i]:用于存储所有right_cur的值都为i村庄的点的下标(和前一个玩意儿互逆似的)。

     当前状态转移f[k][i]=min(f[k-1][j]+Cost(i,j))+c[i]完成后,我们将以i为 right_cur的值的点全部枚举一遍(使用数组cur_right),对于每个枚举的点再依靠left_cur[i]找到最左边能够覆盖该点的村庄下标p。这样做是干啥呢?因为i即将循环至i+1那么下面的状态对于所有(P,I)[P<p,I>i]都不可能覆盖这些枚举的点了,那么这些点的w(额外费用)必然会贡献Cost所以我们先给这些点(即1~p-1的点)加上这个Cost,然后维护这些f[k-1][]+Cost的最小值用于下一次转移就可以了——用啥维护可以支持区间加和区间求最值?线段树!

      代码长出来了:

 #include<vector>
#include<stdio.h>
#include<algorithm>
#define ll long long
#define inf 1ll*100000000*100000000
#define In(a,p) go(i,p,n)scanf("%d",a+i)
#define go(i,a,b) for(int i=a;i<=b;i++)
#define ro(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=;int n,K;
ll d[N],c[N],s[N],w[N],f[N],left_cur[N],ans=inf,W;
vector<int>cur_right[N];
struct Binary_Search
{
int l,r,M,res;
int Left_Search(int i){l=,r=res=i;
while(l<=r)d[i]-d[M=l+r>>]<=s[i]?res=M,r=M-:l=M+;return res;}
int Right_Search(int i){l=res=i,r=n;
while(l<=r)d[M=l+r>>]-d[i]<=s[i]?res=M,l=M+:r=M-;return res;}
}dichotomy;
struct Segment_Tree
{
int sz,lch[N*],rch[N*];ll lazy[N*],Min[N*];
void Init(){sz=;int _;Build(_,,n);}
void Push_Up(int u){Min[u]=min(Min[lch[u]],Min[rch[u]]);}
void Push_Down(int u)
{
lazy[lch[u]]+=lazy[u];lazy[rch[u]]+=lazy[u];
Min[lch[u]]+=lazy[u];Min[rch[u]]+=lazy[u];lazy[u]=;
}
void Build(int &u,int l,int r)
{
lazy[u=++sz]=;if(l==r){Min[u]=f[l];return;}
int M=l+r>>;Build(lch[u],l,M);Build(rch[u],M+,r);Push_Up(u);
}
void Update(int u,int l,int r,int L,int R,ll d)
{
if(L>R)return;Push_Down(u);
if(l==L&&r==R){Min[u]+=d;lazy[u]+=d;return;}
int M=l+r>>;if(R<=M)Update(lch[u],l,M,L,R,d);
else if(L>M)Update(rch[u],M+,r,L,R,d);
else Update(lch[u],l,M,L,M,d),Update(rch[u],M+,r,M+,R,d);Push_Up(u);
}
ll Query(int u,int l,int r,int L,int R)
{
if(L>R)return ;Push_Down(u);
if(l==L&&r==R){return Min[u];}
int M=l+r>>;if(R<=M)return Query(lch[u],l,M,L,R);
else if(L>M)return Query(rch[u],M+,r,L,R);
else return min(Query(lch[u],l,M,L,M),Query(rch[u],M+,r,M+,R));Push_Up(u);
}
}maintain;
int main()
{
scanf("%d%d",&n,&K);In(d,);In(c,);In(s,);In(w,); go(i,,n)
{
left_cur[i]=dichotomy.Left_Search(i);
int right=dichotomy.Right_Search(i);
cur_right[right].push_back(i);
}
n++;K++;w[n]=d[n]=inf; go(i,,n){f[i]=c[i];go(j,,i-)if(d[j]+s[j]<d[i])f[i]+=w[j];}ans=f[n];
go(k,,K)
{
maintain.Init();go(i,,n)
{
f[i]=maintain.Query(,,n,,i-)+c[i];
if(cur_right[i].size())go(j,,cur_right[i].size()-)
{
int Pos=cur_right[i][j];
maintain.Update(,,n,,left_cur[Pos]-,w[Pos]);
}
}
ans=min(ans,f[n]);
}
printf("%lld\n",ans);return ;
}//Paul_Guderian

有人说大米饼喜欢压代码,这里有一份不压的————我也会写不压的!

 #include<vector>
#include<stdio.h>
#include<algorithm>
#define ll long long
#define inf 1ll*100000000*100000000
#define In(a,p) go(i,p,n)scanf("%d",a+i)
#define go(i,a,b) for(int i=a;i<=b;i++)
#define ro(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=;int n,K;
ll d[N],c[N],s[N],w[N],f[N],left_cur[N],ans=inf,W;
vector<int>cur_right[N];
struct Binary_Search
{
int l,r,mid;
int Left_Search(int i)
{
l=,r=i;int res=i;
while(l<=r)
{
mid=l+r>>;
if(d[i]-d[mid]<=s[i])res=mid,r=mid-;
else l=mid+;
}
return res;
}
int Right_Search(int i)
{
l=i,r=n;int res=i;
while(l<=r)
{
mid=l+r>>;
if(d[mid]-d[i]<=s[i])res=mid,l=mid+;
else r=mid-;
}
return res;
}
}dichotomy;
struct Segment_Tree
{
int sz,lch[N*],rch[N*];ll lazy[N*],Min[N*];
void Init(){sz=;int _;Build(_,,n);}
void Push_Up(int u)
{
Min[u]=min(Min[lch[u]],Min[rch[u]]);
}
void Push_Down(int u)
{
lazy[lch[u]]+=lazy[u];
lazy[rch[u]]+=lazy[u];
Min[lch[u]]+=lazy[u];
Min[rch[u]]+=lazy[u];
lazy[u]=;
}
void Build(int &u,int l,int r)
{
lazy[u=++sz]=;
if(l==r)
{
Min[u]=f[l];
return;
}
int M=l+r>>;
Build(lch[u],l,M);
Build(rch[u],M+,r);
Push_Up(u);
}
void Update(int u,int l,int r,int L,int R,ll d)
{
if(L>R)return;
Push_Down(u);
if(l==L&&r==R)
{
Min[u]+=d;
lazy[u]+=d;
return;
}
int M=l+r>>;
if(R<=M)Update(lch[u],l,M,L,R,d);
else if(L>M)Update(rch[u],M+,r,L,R,d);
else Update(lch[u],l,M,L,M,d),Update(rch[u],M+,r,M+,R,d);
Push_Up(u);
}
ll Query(int u,int l,int r,int L,int R)
{
if(L>R)return ;
Push_Down(u);
if(l==L&&r==R)
{
return Min[u];
}
int M=l+r>>;
if(R<=M)return Query(lch[u],l,M,L,R);
else if(L>M)return Query(rch[u],M+,r,L,R);
else return min(Query(lch[u],l,M,L,M),Query(rch[u],M+,r,M+,R));
Push_Up(u);
}
}maintain;
void Input_Data()
{
scanf("%d%d",&n,&K);
In(d,);In(c,);In(s,);In(w,);
}
void Add_Ans_Point()
{
n++;K++;
w[n]=d[n]=inf;
}
void Pre_Handle_of_Dynamic_Programming()
{
go(i,,n)
{
left_cur[i]=dichotomy.Left_Search(i);
int right=dichotomy.Right_Search(i);
cur_right[right].push_back(i);
}
Add_Ans_Point();
}
void Init_First_Status()
{
go(i,,n)
{
f[i]=c[i];
go(j,,i-)
{
if(d[j]+s[j]<d[i])
{
f[i]+=w[j];
}
}
}
}
void Optimized_Dynamic_Programming()
{
Init_First_Status();
ans=f[n];
go(k,,K)
{
maintain.Init();
go(i,,n)
{
f[i]=maintain.Query(,,n,,i-)+c[i];
if(cur_right[i].size())
{
go(j,,cur_right[i].size()-)
{
int Pos=cur_right[i][j];
maintain.Update(,,n,,left_cur[Pos]-,w[Pos]);
}
}
}
ans=min(ans,f[n]);
}
printf("%lld\n",ans);
}
int main()
{
freopen("in.in","r",stdin); Input_Data(); Pre_Handle_of_Dynamic_Programming(); Optimized_Dynamic_Programming(); return ;
}//Paul_Guderian

【条理清晰?】


     

我无法忘记那只廉价的吉他

和那件破旧的蓝色军装。————汪峰《雨天的回忆》

【Bzoj 1835 基站选址】的更多相关文章

  1. BZOJ 1835 基站选址(线段树优化DP)

    题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1835 题意:有N个村庄坐落在一条直线上,第 i(i>1)个村庄距离第1个村庄的距离 ...

  2. BZOJ 1835 基站选址(DP+线段树)

    # include <cstdio> # include <cstring> # include <cstdlib> # include <iostream& ...

  3. BZOJ 1835: [ZJOI2010]base 基站选址 [序列DP 线段树]

    1835: [ZJOI2010]base 基站选址 题目描述 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立 ...

  4. bzoj[1835][ZJOI2010]base 基地选址

    bzoj[1835][ZJOI2010]base 基地选址 标签: 线段树 DP 题目链接 题解 这个暴力DP的话应该很容易看出来. dp[i][j]表示造了i个通讯站,并且j是第i个的最小费用. \ ...

  5. 【BZOJ1835】基站选址(线段树)

    [BZOJ1835]基站选址(线段树) 题面 BZOJ 题解 考虑一个比较暴力的\(dp\) 设\(f[i][j]\)表示建了\(i\)个基站,最后一个的位置是\(j\)的最小代价 考虑如何转移\(f ...

  6. 【题解】Luogu P2605 [ZJOI2010]基站选址

    原题传送门:P2604 [ZJOI2010]基站选址 看一眼题目,变知道这题一定是dp 设f[i][j]表示在第i个村庄修建第j个基站且不考虑i+1~n个村庄的最小费用 可以得出f[i][j] = M ...

  7. 基站选址(base.c/cpp/pas)

    基站选址(base.c/cpp/pas) 题目描述  有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费 ...

  8. 【BZOJ1835】[ZJOI2010]base 基站选址 线段树+DP

    [BZOJ1835][ZJOI2010]base 基站选址 Description 有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di.需要在这些村庄中建立不超过K个通讯 ...

  9. 【LG2605】[ZJOI2010]基站选址

    [LG2605][ZJOI2010]基站选址 题面 洛谷 题解 先考虑一下暴力怎么写,设\(f_{i,j}\)表示当前\(dp\)到\(i\),且强制选\(i\),目前共放置\(j\)个的方案数. 那 ...

随机推荐

  1. Flask 蓝图(Blueprint)

    蓝图使用起来就像应用当中的子应用一样,可以有自己的模板,静态目录,有自己的视图函数和URL规则,蓝图之间互相不影响.但是它们又属于应用中,可以共享应用的配置.对于大型应用来说,我们可以通过添加蓝图来扩 ...

  2. DML数据操作语言之谓词,case表达式

    谓词:就是返回值是真值的函数. 前面接触到的“>” “<” “=”等称为比较运算符,它们的正式名称就是比较谓词.因为它们比较之后返回的结果是真值. 由于谓词 返回的结果是一个真值 ,即tr ...

  3. Python 迭代器之列表解析

     [TOC] 尽管while和for循环能够执行大多数重复性任务, 但是由于序列的迭代需求如此常见和广泛, 以至于Python提供了额外的工具以使其更简单和高效. 迭代器在Python中是以C语言的 ...

  4. Python内置函数(34)——filter

    英文文档: filter(function, iterable) Construct an iterator from those elements of iterable for which fun ...

  5. OrientDB入门(1)Getting Started

    Running OrientDB the First Time First, download and extract OrientDB by selecting the appropriate pa ...

  6. Spring Security入门(3-3)Spring Security 手工配置并注入 authenticationProvider 和 异常信息传递

    特别注意的是 这样就能保证抛出UsernameNotFoundException时,前台显示出错信息: 另外,ps:

  7. sass的简介,安装,语法。

    一,sass的简介 1,Sass完全兼容所有版本的CSS.我们对此严格把控,所以你可以无缝地使用任何可用的CSS库. 2,Sass已经经过其核心团队超过8年的精心打造. 3,有无数的框架使用Sass构 ...

  8. NHibernate从入门到精通系列(3)——第一个NHibernate应用程序

    内容摘要 准备工作 开发流程 程序开发 一.准备工作 1.1开发环境 开发工具:VS2008以上,我使用的是VS2010 数据库:任意关系型数据库,我使用的是SQL Server 2005 Expre ...

  9. Mysql中autocommit的用法

    定义 Mysql文档原文:SET autocommit disables or enables the default autocommit mode for the current session. ...

  10. SpringBoot(二):设置springboot同一接口程序启动入口

    根据上一篇文章中搭建了一个springboot简单工程,在该工程中编写HelloWordController.java接口类,并在该类中写了一个main函数,做为该类的接口服务启动入口.此时如果新增多 ...