很不错的一道题 用线段树优化! 学习了怎样插入顺序,使得不重复计算 看了edwardmj ,还有请教了zlqiszlq93,他几句话就说得很清楚了....好膜拜!!!!
/**//* 题意:N个村在一条直线上,位置为D[i] 现需要建K个站,在村i建站花费为C[i] 在村i的[ D[i]-S[i],D[i]+S[i] ]范围内只要有站,村i就算被覆盖了 如果没被覆盖需要赔偿W[i] 问最小代价 F[k,i]表示在前i个村建立k个站的最小代价,且村i有站(只考虑前i个的,后面覆盖不到需要赔偿的先不计) F[k,i] = min{ F[k-1,j] + W[j,i] } + C[i] W[j,i]指在j+1到i-1之间的,不能被j且i覆盖到村的赔偿费和 O(KN^2) K<=100,N<=20000 zlqiszlq93: 核心思想 就是利用线段树 动态的维护一个最优策略 令F[i]前i个点且在i有站的代价为多少那么 F[i] = min(F[j])+W(j,i) W(j,i)属于补贴的钱 然后 分析 点p 他需要补贴钱 当且仅当i不能覆盖它,且j也不能覆盖它 那么 当i不能覆盖他时 给(1,j)+上补贴p的钱 j为最大的不能覆盖点p的站
实现上,如果D[p]+S[p]<D[i],那么D[p]+S[p]也会<D[ii] ii>i 所以p是不用回退的,在i之前插入的p,对于i来说也是有效的(也是i不能覆盖的),不用再插入 while(Ed[p]<D[i]) 给(1,j)+W[p] 这里需要成段加上,用线段树
D[p]-S[p]及D[p]+S[p]都不一定是单调的,需要先排序 first[p]记录左边第一个能覆盖p的站
虚设一个第0个站,在第0个站已建立基站,覆盖0及之前的,代价当然为0
启示: 在点i之前插入的W[i]对于以后的ii(ii>i)都是有效的; 由于st[p],ed[p]不单调,需先排序; 虚设一个F[0],这样k=0时,0之后的都是用W[i],跟k=0的定义符合; F[k,i] = min{ F[k-1,j] + W[j,i] } + C[i] 如果直接枚举,发现某些代价W[p]对于(j,i)需要计算的话,那么对于(jj,i) jj<j 同样需要计算 就是说代价W[p]很多j都需要加上,所以成段更新 而直接枚举,是枚举了j然后去找p 这里是对于每个p考虑需要加上的j,考虑角度不同!! 考察每个p究竟使得那些j需要加上W[p] */ #include<cstdio> #include<cstring> #include<algorithm>
using namespace std;
const int MAXN = 20010; const int INF = 1000000000;
inline int min(int a,int b){return a<b?a:b;}
struct Pos { int id,pos; void set(int _id,int _pos) { id=_id; pos=_pos; } bool operator<(const Pos &B)const { return pos<B.pos; } };
struct Node { int left,right; int Min,delta; };
struct SegTree { Node nodes[MAXN*4]; Node *root;
int Min() { return root->Min; } void build(int left,int right,int *ary) { root=&nodes[1]; build(1,left,right,ary); } void insert(int left,int right,int x) { if(left>right)return; insert(1,left,right,x); }
void pushdown(int p)//push p to two sons { if(nodes[p].left==nodes[p].right||nodes[p].delta==0)return; nodes[2*p].delta+=nodes[p].delta; nodes[2*p].Min+=nodes[p].delta; nodes[2*p+1].delta+=nodes[p].delta; nodes[2*p+1].Min+=nodes[p].delta; nodes[p].delta=0; } void update(int p) { pushdown(p); nodes[p].Min=min(nodes[2*p].Min,nodes[2*p+1].Min); } void build(int p,int left,int right,int *ary) { nodes[p].left=left; nodes[p].right=right; nodes[p].delta=0; if(left==right) { nodes[p].Min=ary[left]; return ; } int mid=(left+right)>>1; build(2*p,left,mid,ary); build(2*p+1,mid+1,right,ary); update(p); } void insert(int p,int left,int right,int x) { if(left<=nodes[p].left&&nodes[p].right<=right) { nodes[p].delta+=x; nodes[p].Min+=x; return; } int mid=(nodes[p].left+nodes[p].right)>>1; if(left>mid)insert(2*p+1,left,right,x); else if(right<=mid)insert(2*p,left,right,x); else { insert(2*p,left,mid,x); insert(2*p+1,mid+1,right,x); } update(p); } };
int F[MAXN];//F[k,n]表示前n个建立k个,且n有建站,使得前n个代价最小 //注意初始化为INF int D[MAXN],C[MAXN],S[MAXN],W[MAXN];
SegTree segTree; Pos st[MAXN],ed[MAXN]; int first[MAXN];
int main() { //freopen("in","r",stdin); int N,K; while(~scanf("%d%d",&N,&K)) { for(int i=2;i<=N;i++)scanf("%d",&D[i]); for(int i=1;i<=N;i++)scanf("%d",&C[i]); for(int i=1;i<=N;i++)scanf("%d",&S[i]); for(int i=1;i<=N;i++)scanf("%d",&W[i]); for(int i=1;i<=N;i++) { st[i].set(i,D[i]-S[i]); ed[i].set(i,D[i]+S[i]); } sort(st+1,st+1+N); sort(ed+1,ed+1+N); for(int i=1,p=1;i<=N;i++) { while(p<=N&&st[p].pos<=D[i]) { first[st[p++].id]=i; } } int ans = INF; F[0]=0;//在第0个站建立,覆盖前0个站的代价永远都为0 for(int i=1;i<=N;i++)F[i]=INF;//而这时是前i个站没有建立站也没有赔偿村i的代价,就为INF
for(int k=0;k<=K;k++) { int i,p; segTree.build(0,N,F); for(i=1,p=1;i<=N;i++) { for(;p<=N&&ed[p].pos<D[i];p++) { segTree.insert(0,first[ed[p].id]-1,W[ed[p].id]); } F[i]=segTree.Min()+C[i];//现F[i]的值是指F[k+1,i] } for(;p<=N;p++)//把剩下的也插入,使得线段树现在存的是F[i]+W[i,n+1],即真正的代价 segTree.insert(0,first[ed[p].id]-1,W[ed[p].id]); ans=min(ans,segTree.Min());//线段树存的才是真正花费 } printf("%d\n",ans); } return 0; }
|