【洛谷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;
}
posted @ 2021-08-08 09:32  stoorz  阅读(126)  评论(0编辑  收藏  举报