很不錯的一道題 用線段樹優(yōu)化! 學(xué)習(xí)了怎樣插入順序,使得不重復(fù)計(jì)算 看了edwardmj ,還有請教了zlqiszlq93,他幾句話就說得很清楚了....好膜拜!!!!
 /**//*
題意:N個村在一條直線上,位置為D[i] 現(xiàn)需要建K個站,在村i建站花費(fèi)為C[i]
在村i的[ D[i]-S[i],D[i]+S[i] ]范圍內(nèi)只要有站,村i就算被覆蓋了
如果沒被覆蓋需要賠償W[i] 問最小代價
F[k,i]表示在前i個村建立k個站的最小代價,且村i有站(只考慮前i個的,后面覆蓋不到需要賠償?shù)南炔挥?jì))
F[k,i] = min{ F[k-1,j] + W[j,i] } + C[i] W[j,i]指在j+1到i-1之間的,不能被j且i覆蓋到村的賠償費(fèi)和
O(KN^2) K<=100,N<=20000
zlqiszlq93:
核心思想 就是利用線段樹 動態(tài)的維護(hù)一個最優(yōu)策略
令F[i]前i個點(diǎn)且在i有站的代價為多少那么 F[i] = min(F[j])+W(j,i) W(j,i)屬于補(bǔ)貼的錢
然后 分析 點(diǎn)p 他需要補(bǔ)貼錢 當(dāng)且僅當(dāng)i不能覆蓋它,且j也不能覆蓋它
那么 當(dāng)i不能覆蓋他時 給(1,j)+上補(bǔ)貼p的錢 j為最大的不能覆蓋點(diǎn)p的站

實(shí)現(xiàn)上,如果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]都不一定是單調(diào)的,需要先排序
first[p]記錄左邊第一個能覆蓋p的站

虛設(shè)一個第0個站,在第0個站已建立基站,覆蓋0及之前的,代價當(dāng)然為0

啟示:
在點(diǎn)i之前插入的W[i]對于以后的ii(ii>i)都是有效的;
由于st[p],ed[p]不單調(diào),需先排序;
虛設(shè)一個F[0],這樣k=0時,0之后的都是用W[i],跟k=0的定義符合;
F[k,i] = min{ F[k-1,j] + W[j,i] } + C[i]
如果直接枚舉,發(fā)現(xiàn)某些代價W[p]對于(j,i)需要計(jì)算的話,那么對于(jj,i) jj<j 同樣需要計(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個站的代價永遠(yuǎn)都為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];//現(xiàn)F[i]的值是指F[k+1,i]
}
for(;p<=N;p++)//把剩下的也插入,使得線段樹現(xiàn)在存的是F[i]+W[i,n+1],即真正的代價
segTree.insert(0,first[ed[p].id]-1,W[ed[p].id]);
ans=min(ans,segTree.Min());//線段樹存的才是真正花費(fèi)
}
printf("%d\n",ans);
}
return 0;
}
|