很不错的一道题 用线段树优化! 学习了怎样插入顺序,使得不重复计算 看了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;
}
|