动态规划+线段树
神题。
ZJOI2010省选原题。
最朴素的版本:
设dp[i][j]表示在第i个村庄建设第j个基站的最小花费,pay[i][j]表示在i,j区间未被覆盖的基站所需要的罚款之和,C[i]表示在第i个村庄建设基站的花费
[dp_{i,j}=min{dp_{k,j-1}+pay_{k,i}}+C_i(j-1leq k leq i-1)
]
复杂度O(n^2×k)
优化层次一:缩1维(看似没用的优化)
可以看到,第2位j是没有用的。
因此,状态转移方程可以变成这样(j依然要枚举):
[dp_{i}=min{dp_{k}+pay_{k,i}}+C_i(j-1leq k leq i-1)
]
优化层次二:线段树(瞬间降成O(n^log(n)×k))
由于要在[j-1,i-1]取一个最小的k,那么可以用线段树维护一个最小值。
但是,怎么维护pay[k][i]呢。
考虑一下,设st[i]和ed[i]分别表示最左端能覆盖到i和最右端能覆盖到i的村庄编号。
那么当i变成i+1时,对于ed[k]=i的所有村庄k,若i+1在[1,st[k]-1]区间中转移时,肯定是要罚款W[k]的,因此在[1,st[k]-1]中区间加上个W[k]。
那么,我们可以将最外层循环枚举建设了j个基站,对于j=1时要特殊处理,其余的可以在build时把上一层的dp[i]值存入线段树中。
对于在[1,st[k]-1]中区间加上个W[k]可以把所有ed[k]=i的点存入一个vector中,方便修改。
这样就时间复杂度就得到了巨大蜕变。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,D[1000010],C[1000010],S[1000010],W[1000010],ans,dp[100010],st[100010],ed[100010];
vector<int> G[100010];
struct node{
int L,R,sum,lazy;
}tree[1000010];
void Up(int p){
tree[p].sum=min(tree[p<<1].sum,tree[p<<1|1].sum);
}
void Down(int p){
if(tree[p].lazy==0)return;
tree[p<<1].sum+=tree[p].lazy;
tree[p<<1|1].sum+=tree[p].lazy;
tree[p<<1].lazy+=tree[p].lazy;
tree[p<<1|1].lazy+=tree[p].lazy;
tree[p].lazy=0;
}
void build(int L,int R,int p){
tree[p].L=L,tree[p].R=R,tree[p].lazy=0;
int mid=(L+R)>>1;
if(L==R){
tree[p].sum=dp[L];
return;
}
build(L,mid,p<<1);
build(mid+1,R,p<<1|1);
Up(p);
}
void update(int L,int R,int p,int num){
if(tree[p].L==L&&tree[p].R==R){
tree[p].sum+=num;
tree[p].lazy+=num;
return;
}
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(R<=mid)update(L,R,p<<1,num);
else if(L>=mid+1)update(L,R,p<<1|1,num);
else update(L,mid,p<<1,num),update(mid+1,R,p<<1|1,num);
Up(p);
}
int Query(int L,int R,int p){
//cout<<L<<" "<<R<<" "<<p<<endl;
if(tree[p].L==L&&tree[p].R==R)return tree[p].sum;
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(R<=mid)return Query(L,R,p<<1);
else if(L>=mid+1)return Query(L,R,p<<1|1);
else return min(Query(L,mid,p<<1),Query(mid+1,R,p<<1|1));
}
signed main(){
scanf("%lld %lld",&n,&k);
for(int i=2;i<=n;i++)scanf("%lld",&D[i]);
for(int i=1;i<=n;i++)scanf("%lld",&C[i]);
for(int i=1;i<=n;i++)scanf("%lld",&S[i]);
for(int i=1;i<=n;i++)scanf("%lld",&W[i]);
n++,k++;
D[n]=1e9+7;
for(int i=1;i<=n;i++){
st[i]=lower_bound(D+1,D+1+n,D[i]-S[i])-D;
ed[i]=upper_bound(D+1,D+1+n,D[i]+S[i])-D-1;
G[ed[i]].push_back(i);
}
for(int i=1;i<=k;i++){
if(i==1){
int res=0;
for(int j=1;j<=n;j++){
dp[j]=res+C[j];
for(int k=0;k<G[j].size();k++){
int t=G[j][k];
res+=W[t];
}
}
ans=dp[n];
}
else {
build(1,n,1);
for(int j=1;j<=n;j++){
if(j<i)dp[j]=C[j];
else dp[j]=Query(i-1,j-1,1)+C[j];
for(int k=0;k<G[j].size();k++){
int t=G[j][k];
if(st[t]-1>=1)update(1,st[t]-1,1,W[t]);
}
}
ans=min(ans,dp[n]);
}
}
cout<<ans;
return 0;
}