題意
有 $ n $ 個村莊在一排直線上,現在要建造不超過 $ K $ 個通訊基站,基站只能造在村莊處。
第 $ i $ 個村莊距離第 $ 1 $ 個村莊的距離為 $ D_i $ 。在此建造基站的費用為 $ C_i $ 。如果在此不超過 $ S_i $ 的範圍內有基站,那麼這個村莊就被覆蓋了。如果它沒有被覆蓋,則需要花費 $ W_i $ 的補償費用。
問你最小總花費是多少。
題解
首先有一個很顯然的dp:
$ dp[i][j] $ 表示在第 $ i $ 個村莊建了基站,此時一共建了 $ j $ 個基站,第 $ i $ 個以及之前村莊的最小花費。
轉移為:
\[
dp[i][j] = min(dp[k][j-1] + cost(k,i)) \quad (k<i)
\]
其中 $ cost(k,i) $ 表示如果在 $ k $ 和 $ i $ 建了基站,並且它們之間沒有建基站,此時村莊 $ k $ 到村莊 $ i $ 之間的花費,即:
\[
cost(i,j) = \sum_{k=i}^j W_k * [|D_k-D_i|>S_k \text{ } \& \text{ } |D_k-D_j|>S_k]
\]
然後考慮如何優化。
先定義 $ l_i $ 和 $ r_i $ 分別表示能夠覆蓋村莊 $ i $ 的最左和最右的基站建造位置,可以直接二分出來。
我們希望對於每個 $ j $ 來說,在 $ i $ 不斷遞增的過程中,動態地維護 $ F(k) = dp[k][j-1] + cost(k,i) $ 的最小值。
假設當前已經求出了 $ dp[i-1][j] $ ,接下來該求 $ dp[i][j] $ 了。
那麼對於所有滿足 $ r_p = i-1 $ 的 $ p $ 來說,所有 $ F(k) \quad (k \in [1,l_p-1]) $ 都應該加上 $ W_p $ 。也就是將基站從 $ i-1 $ 移動到 $ i $ 之後,給對應的 $ F(k) $ 加上了那些本來被覆蓋但是現在不覆蓋的村莊的補償費。
所以對於當前的 $ i $ 來說,$ dp[i][j] = min(F(k)) \quad (k \in [1,i-1]) $
由於有區間加法和查區間最小值,所以要用線段樹維護 $ F(k) $ 。對於每個 $ j $ 來說,在一開始重建一下線段樹就好。
AC Code
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define MAX_N 20005
#define MAX_V 80005
#define INF 1000000000
#define int long long
using namespace std;
int n,K,ans;
int d[MAX_N];
int c[MAX_N];
int s[MAX_N];
int w[MAX_N];
int l[MAX_N];
int r[MAX_N];
int dp[MAX_N];
int dat[MAX_V];
int tag[MAX_V];
vector<int> v[MAX_N];
void read()
{
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]);
d[++n]=INF,w[n]=INF,K++;
}
void cal_lr()
{
for(int i=1;i<=n;i++)
{
l[i]=lower_bound(d+1,d+1+n,d[i]-s[i])-d;
r[i]=upper_bound(d+1,d+1+n,d[i]+s[i])-d-1;
v[r[i]].push_back(i);
}
}
void push_down(int k)
{
if(tag[k])
{
dat[k*2+1]+=tag[k];
dat[k*2+2]+=tag[k];
tag[k*2+1]+=tag[k];
tag[k*2+2]+=tag[k];
tag[k]=0;
}
}
void push_up(int k)
{
dat[k]=min(dat[k*2+1],dat[k*2+2]);
}
void build(int l,int r,int k)
{
if(l==r)
{
dat[k]=dp[l];
tag[k]=0;
return;
}
int mid=(l+r)>>1;
build(l,mid,k*2+1);
build(mid+1,r,k*2+2);
push_up(k);
tag[k]=0;
}
void update(int a,int b,int k,int l,int r,int x)
{
if(a<=l && r<=b)
{
dat[k]+=x;
tag[k]+=x;
return;
}
push_down(k);
int mid=(l+r)>>1;
if(a<=mid) update(a,b,k*2+1,l,mid,x);
if(b>mid) update(a,b,k*2+2,mid+1,r,x);
push_up(k);
}
int query(int a,int b,int k,int l,int r)
{
if(a<=l && r<=b) return dat[k];
push_down(k);
int mid=(l+r)>>1,ans=INF;
if(a<=mid) ans=min(ans,query(a,b,k*2+1,l,mid));
if(b>mid) ans=min(ans,query(a,b,k*2+2,mid+1,r));
return ans;
}
void cal_dp()
{
int sum=0;
for(int i=1;i<=n;i++)
{
dp[i]=sum+c[i];
for(int j=0;j<v[i].size();j++) sum+=w[v[i][j]];
}
ans=dp[n];
for(int j=2;j<=K;j++)
{
build(1,n,0);
for(int i=1;i<=n;i++)
{
if(i>1) dp[i]=query(1,i-1,0,1,n)+c[i];
else dp[i]=c[i];
for(int k=0;k<v[i].size();k++)
{
int t=v[i][k];
if(l[t]>1) update(1,l[t]-1,0,1,n,w[t]);
}
}
ans=min(ans,dp[n]);
}
}
void work()
{
cal_lr();
cal_dp();
printf("%lld\n",ans);
}
signed main()
{
read();
work();
}