P2605 [ZJOI2010]基站选址

P2605 [ZJOI2010]基站选址

 

题目描述

详见:P2605 [ZJOI2010]基站选址

Solution

首先不难想到一个  O(n^2k)  的DP。

f[i][j]  表示前i个村庄选择了j个基站的总费用。

f[i][j]=min(f[i][k]+cost(k,i))+c[i]

考虑如何优化这个转移。

对于村庄i,我们记录它覆盖范围内最靠前的村庄  fi[i]  和最靠后的村庄  la[i] 。

倘若在la[i]之后的村庄建立基站,则若上一个基站在fi[i]之前,那么就会多一个w[i]的费用。

增加费用的状态是形如f[1][j-1]...f[\;\;fi[i]\;\;][j-1] 的连续一段状态。

我们只需要让f滚动,维护区间加,询问区间最小值,线段树维护即可。

 

Ps:luogu题解中很多代码求  la[i]   时使用的是lower\_\;bound 再特判减1,但这样如果出现相等的值时,会找到相等的值中第一个值,与  la[i]  的实际意义不符,但由于我太蒟,不知道会不会对答案产生影响qwq。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+50;
const int INF=0x3f3f3f3f;
int d[MAXN],a[MAXN],s[MAXN],w[MAXN],fi[MAXN],la[MAXN],f[MAXN];
vector<int> e[MAXN];
inline int read()
{
    int x=0,f=1; char c=getchar();
    while (c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); }
    while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
    return x*f;
}
struct Segment_Tree
{
	struct segnode{int l,r,tag,mn; } tree[MAXN<<2];
	void up(int x){ tree[x].mn=min(tree[x<<1].mn,tree[x<<1|1].mn); }
	void down(int x)
	{
		if (tree[x].tag!=0)
		{
			int q=tree[x].tag;
			tree[x<<1].tag+=q,tree[x<<1].mn+=q;
			tree[x<<1|1].tag+=q,tree[x<<1|1].mn+=q;
			tree[x].tag=0;
		}
	}
	void build(int x,int l,int r)
	{
		tree[x].tag=tree[x].mn=0;
		if ((tree[x].l=l)==(tree[x].r=r)) 
		{
			tree[x].mn=f[l];
		    return;
		}
		int mid=(l+r)>>1;
		build(x<<1,l,mid);
		build(x<<1|1,mid+1,r);
		up(x);
	}
	int query(int x,int l,int r)
	{
		if (tree[x].l>=l&&tree[x].r<=r)  return tree[x].mn;
		down(x);
		int mid=(tree[x].l+tree[x].r)>>1;
		if (r<=mid) return query(x<<1,l,r);
		else if (l>mid) return query(x<<1|1,l,r);
		else return min(query(x<<1,l,mid),query(x<<1|1,mid+1,r));
	}
	void change(int x,int l,int r,int y)
	{
		if (tree[x].l>=l&&tree[x].r<=r)
		{
			tree[x].mn+=y;
			tree[x].tag+=y;
			return;
		}
		down(x);
		int mid=(tree[x].l+tree[x].r)>>1;
		if (r<=mid) change(x<<1,l,r,y);
		else if (l>mid) change(x<<1|1,l,r,y);
		else change(x<<1,l,mid,y),change(x<<1|1,mid+1,r,y);
		up(x);
	}	
} segment;
int main()
{
	int n=read(),m=read(); 
	for (int i=2;i<=n;i++) d[i]=read();
	for (int i=1;i<=n;i++) a[i]=read();
	for (int i=1;i<=n;i++) s[i]=read();
	for (int i=1;i<=n;i++) w[i]=read();
	
	n++; 
	d[n]=w[n]=INF;
	for (int i=1;i<=n;i++)
	{
		fi[i]=lower_bound(d+1,d+n+1,d[i]-s[i])-d;
		la[i]=upper_bound(d+1,d+n+1,d[i]+s[i])-d-1;
		//cout<<i<<" "<<fi[i]<<" "<<la[i]<<endl;
		e[la[i]].push_back(i);
	}
	
	int cnt=0,ans=0;
	for (int i=1;i<=n;i++)
	{
		f[i]=cnt+a[i];
		for (int k=0;k<e[i].size();k++) cnt+=w[e[i][k]];
	}
	ans=f[n];
	//for (int i=1;i<=n;i++) cout<<i<<" "<<f[i]<<endl;cout<<endl;
	for (int j=2;j<=m+1;j++)
	{
		segment.build(1,1,n);
		for (int i=1;i<=n;i++)
		{
			if (j>i) f[i]=a[i];
			else f[i]=segment.query(1,j-1,i-1)+a[i];
			for (int k=0;k<e[i].size();k++) 
			    if (fi[e[i][k]]>1) segment.change(1,1,fi[e[i][k]]-1,w[e[i][k]]);
		}
		ans=min(ans,f[n]);
	}
	printf("%d\n",ans);
	return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值