[线段树优化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 }