【洛谷P2605】基站选址
题目
题目链接:https://www.luogu.com.cn/problem/P2605
有 \(N\) 个村庄坐落在一条直线上,第 \(i(i>1)\) 个村庄距离第 \(1\) 个村庄的距离为 \(D_i\)。需要在这些村庄中建立不超过 \(K\) 个通讯基站,在第 \(i\) 个村庄建立基站的费用为 \(C_i\)。如果在距离第 \(i\) 个村庄不超过 \(S_i\) 的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第 \(i\) 个村庄没有被覆盖,则需要向他们补偿,费用为 \(W_i\)。现在的问题是,选择基站的位置,使得总费用最小。
\(N\leq 20000,K\leq 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]\times [d_l-s_l>d_k]\times 1\right)
\]
直接转移是 \(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(nk\log 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<<"\n";
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;
}