题目
题目链接:https://www.luogu.com.cn/problem/P2605
有 (N) 个村庄坐落在一条直线上,第 (i(i>1)) 个村庄距离第 (1) 个村庄的距离为 (D_i)。需要在这些村庄中建立不超过 (K) 个通讯基站,在第 (i) 个村庄建立基站的费用为 (C_i)。如果在距离第 (i) 个村庄不超过 (S_i) 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 (i) 个村庄没有被覆盖,则需要向他们补偿,费用为 (W_i)。现在的问题是,选择基站的位置,使得总费用最小。
(Nleq 20000,Kleq 100)。
思路
设 (f[i][j]) 表示在前 (i) 个村庄,安装了 (j) 个通讯基站的最小代价。
考虑枚举上一次选择安装基站的位置 (k),有转移
[f[i][j]=min_{k<i}left(f[k][j-1]+sum^{i-1}_{l=k+1}[d_l+s_l<d_i] imes [d_l-s_l>d_k] imes 1
ight)
]
直接转移是 (O(n^2k)) 的,看到区间最小值,考虑维护 (k) 棵线段树优化转移。
对于一个位置 (k(k<i)),如果满足 (d_k+s_k<d_i),那么对于 (d_k-s_k>d_j) 的所有 (j),从 (f[j][l-1]) 转移到 (f[i][l]) 的时候都会算上 (w_k) 的贡献。所以我们可以按照 (d_i-s_i) 从小到大排序,当我们枚举到 (i) 时,维护一个指针 (j),把所有 (d_j+s_j<d_i) 的 (j),在线段树上区间 ([1,d_j-s_j-1]) 加上 (w_j) 即可。
时间复杂度 (O(nklog n))。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=20010,M=101,Inf=2e9;
int n,m,ans,sum,d[N],c[N],s[N],w[N],id[N],L[N],f[N][M];
bool cmp1(int x,int y)
{
return d[x]+s[x]<d[y]+s[y];
}
bool cmp2(int x,int y)
{
return d[x]-s[x]>d[y]-s[y];
}
struct SegTree
{
int lazy[N*4],minn[N*4];
void pushdown(int x)
{
if (lazy[x])
{
minn[x*2]+=lazy[x]; minn[x*2+1]+=lazy[x];
lazy[x*2]+=lazy[x]; lazy[x*2+1]+=lazy[x];
lazy[x]=0;
}
}
void update(int x,int l,int r,int ql,int qr,int v)
{
if (ql>qr) return;
if (ql<=l && qr>=r)
return (void)(lazy[x]+=v,minn[x]+=v);
pushdown(x);
int mid=(l+r)>>1;
if (ql<=mid) update(x*2,l,mid,ql,qr,v);
if (qr>mid) update(x*2+1,mid+1,r,ql,qr,v);
minn[x]=min(minn[x*2],minn[x*2+1]);
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql>qr) return 0;
if (ql<=l && qr>=r) return minn[x];
pushdown(x);
int mid=(l+r)>>1,res=Inf;
if (ql<=mid) res=min(res,query(x*2,l,mid,ql,qr));
if (qr>mid) res=min(res,query(x*2+1,mid+1,r,ql,qr));
return res;
}
}seg[M];
int main()
{
cerr<<(sizeof(seg)+sizeof(f))/1024/1024<<"
";
scanf("%d%d",&n,&m);
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++)
{
id[i]=i; ans+=w[i];
L[i]=lower_bound(d+1,d+1+n,d[i]-s[i])-d-1;
}
sort(id+1,id+1+n,cmp1);
for (int i=1,k=1;i<=n;i++)
{
for (;k<=n && d[id[k]]+s[id[k]]<d[i];k++)
{
sum+=w[id[k]];
for (int j=0;j<=m;j++)
seg[j].update(1,1,n,1,L[id[k]],w[id[k]]);
}
f[i][1]=c[i]+sum;
seg[1].update(1,1,n,i,i,f[i][1]);
for (int j=2;j<=m;j++)
{
f[i][j]=seg[j-1].query(1,1,n,1,i-1)+c[i];
seg[j].update(1,1,n,i,i,f[i][j]);
}
}
sum=0;
sort(id+1,id+1+n,cmp2);
for (int i=n;i>=1;i--) w[i]+=w[i+1];
for (int i=n,k=1;i>=1;i--)
{
for (;k<=n && d[id[k]]-s[id[k]]>d[i];k++)
sum+=w[id[k]]-w[id[k]+1];
for (int j=1;j<=m;j++)
ans=min(ans,f[i][j]+sum);
}
cout<<ans;
return 0;
}