[线段树优化DP][ZJOI2010]基站选址

时间:2019-11-14
本文章向大家介绍[线段树优化DP][ZJOI2010]基站选址,主要包括[线段树优化DP][ZJOI2010]基站选址使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目描述

有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di。需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci。如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi。现在的问题是,选择基站的位置,使得总费用最小。

输入格式

输入文件的第一行包含两个整数N,K,含义如上所述。

第二行包含N-1个整数,分别表示D2,D3,…,DN ,这N-1个数是递增的。

第三行包含N个整数,表示C1,C2,…CN。

第四行包含N个整数,表示S1,S2,…,SN。

第五行包含N个整数,表示W1,W2,…,WN。

输出格式

输出文件中仅包含一个整数,表示最小的总费用。

输入输出样例

输入
3 2
1 2
2 3 2
1 1 0
10 20 30

输出

4

说明/提示

40%的数据中,N<=500;

100%的数据中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。

Solution

考虑朴素DP

f[i][j]表示只考虑前i个村庄且必在i村建基站,已经建设j个基站的最小费用

这样会忽略最后一个基站对之后村庄的影响

所以我们在无穷远处建一个假村假站,无费用,

就没有以上弊端,max f[n][1--k]即为答案

转移方程 f[i][j]=min(f[k][j-1]+cost[k][i])    

              cost[k][i]为在k和i建基站,中间不建的赔偿花费,是可计算的常数

j这一维可以滚动掉,我们得到了时间复杂度O(n*n*k)的朴素DP算法

考虑优化,取最小值显然可以用线段树维护

问题转化为如何计算cost[k][i]

考虑一个村庄何时可以获得赔偿  显然是枚举到向右距离超过si的村庄, 且用来转移的k距离超过si时
注意到这是一个区间加, 所以我们用邻接表存储这样的操作,时间复杂度O(k*nlogn)完了。。
 
总结 1、建虚点避免后效性,方便设状态
        2、线段树优化DP,要记住格式
  1 #include<cstdio>
  2 #include<algorithm>
  3 using namespace std;
  4 const int N=20005,inf=0x3f3f3f3f;
  5 int n,k,d[N],c[N],s[N],w[N],f[N];
  6 
  7 int num,last[N],nxt[N],ord[N];
  8 inline void add(int x,int hao){nxt[++num]=last[x]; last[x]=num; ord[num]=hao;}
  9 int st[N],ed[N];
 10 inline void pre()
 11  {for(int i=1;i<=n;i++)
 12    {st[i]=lower_bound(d+1,d+n+1,d[i]-s[i])-d;
 13     ed[i]=upper_bound(d+1,d+n+1,d[i]+s[i])-d-1;
 14     add(ed[i],i);
 15    } // 在往ed后就不能覆盖这些基站了,要考虑赔偿 
 16  }
 17  
 18  
 19 struct point{int l,r,minn,lazy;}t[4*N]; 
 20 
 21 
 22 inline void pushup(int i) {t[i].minn=min(t[2*i].minn,t[2*i+1].minn); } 
 23 
 24 inline void pass(int i,int x) {t[i].minn+=x; t[i].lazy+=x;}
 25 
 26 inline void pushdown(int i)
 27   {if(!t[i].lazy) return;
 28    pass(2*i,t[i].lazy); pass(2*i+1,t[i].lazy);
 29    t[i].lazy=0;
 30   }
 31   
 32   
 33   
 34 void build(int i,int l,int r)
 35   {t[i].l=l; t[i].r=r;  t[i].lazy=0;
 36   
 37    if(l==r) {t[i].minn=f[r]; return;}
 38    
 39    int mid=t[i].l+t[i].r>>1;
 40    build(2*i,l,mid); build(2*i+1,mid+1,r);
 41    
 42    pushup(i);
 43   }
 44 
 45 void add(int i,int l,int r,int x)
 46  {if(l>r) return;
 47   if(l<=t[i].l && t[i].r<=r) {pass(i,x); return;}
 48   
 49   int mid=t[i].l+t[i].r>>1; pushdown(i);
 50   
 51   if(l<=mid) add(2*i,l,r,x);
 52   if(mid<r)  add(2*i+1,l,r,x);
 53   
 54   pushup(i);
 55  } 
 56  
 57  
 58 int ask(int i,int l,int r)
 59  {if(l>r) return 0;
 60   if(l<=t[i].l && t[i].r<=r) return t[i].minn;
 61   int mid=t[i].l+t[i].r>>1; pushdown(i);
 62   
 63   if(r<=mid) return ask(2*i,l,r);
 64   if(mid<l)  return ask(2*i+1,l,r);
 65              return min(ask(2*i,l,r),ask(2*i+1,l,r));
 66   pushup(i);
 67  }
 68 inline void hehe()
 69  {
 70   int cmp=0;    
 71   //  手动处理只能选一个基站的边界DP值,相当于每次必须从f[0]=0转移 
 72   for(int i=1;i<=n;i++)    
 73    {f[i]=cmp+c[i];
 74     
 75     for(int j=last[i];j;j=nxt[j])cmp+=w[ord[j]];
 76    }
 77   int ans=f[n]; 
 78   for(int i=2;i<=k;i++)
 79    {build(1,1,n);
 80    
 81     for(int j=1;j<=n;j++)
 82      {if (i>j) f[j]=inf;
 83       else f[j]=ask(1,i-1,j-1)+c[j];
 84       
 85       for(int p=last[j];p;p=nxt[p])
 86         add(1,1,st[ord[p]]-1,w[ord[p]]);
 87         
 88      }
 89     ans=min(ans,f[n]); 
 90    }
 91   printf("%d",ans);     
 92  } 
 93 int main()
 94  {
 95  scanf("%d%d",&n,&k); k++;
 96  for(int i=2;i<=n;i++) scanf("%d",&d[i]);
 97  for(int i=1;i<=n;i++) scanf("%d",&c[i]);
 98  for(int i=1;i<=n;i++) scanf("%d",&s[i]);
 99  for(int i=1;i<=n;i++) scanf("%d",&w[i]);
100  n++; d[n]=w[n]=inf;  
101     //当我们推导i时,我们只考虑了它和前面的基站产生的影响
102     //这时对于最后一个基站我们不会考虑它和之后的村庄产生的影响
103     //则我们可以在最后增加一个村庄
104     //保证它必定被作为基站(无建设费用)且不对前面产生影响
105     //这样就不会有遗漏的了 
106  pre(); 
107  hehe();
108 return 0;
109  }
 

原文地址:https://www.cnblogs.com/YuXiaoze/p/11858630.html