[ZJOI2010]基站选址(DP+线段树)

题目

洛谷P2605

题解

听说是非常经典的DP+线段树题,就来瞻仰一下,果然思维难度还是不小的
这道题也让我对线段树有了更深的了解,不仅是明面上是区间修改的需要用到线段树,甚至像DP这种算法,当算法时间复杂度太高的时候,可以用线段树来处理DP的数据,达到简化DP的目的
这道题的递推公式:f[i]=min{f[i-1]+w[i-1][j]}+c[i]
f[i]为假如第i个点造基站的最小费用,w[x][y]为x和y造了基站后中间的收不到信号的赔偿费用
一看到递推式中出现w这种二维数组,一般直接DP,大多TLE,但是这里可以看到的是当i增加的时候,f数组是不会变得,只有w数组会变,会增加(因为最后一个基站的位置往后移了,会增加中间的赔偿费用),所以我们记录第i个节点上基站最左和最右到达的基站数,然后假如计算完i后,i+1,对R[x]=i的点(这些点就是最远只能到达i号节点,到达不了i+1号节点)的L[x]前全部加上w[x](之前i上的基站能覆盖到x,但i+1的基站就覆盖不到x了,L[x]向左的基站也覆盖不到a,所以a就被抛弃了,得加上他的损失值)
小trick:

  • 因为最后的结果是 f[n],但是第n个基站不一定选取,可以在最后添加一个肯定选取的虚拟基站,n+1,距离为INF,不对前面的基站造成影响~~
    别忘了n+1后k也要+1啊,Wa了我无数次
  • 和一般的线段树相比,可能还要加判断L>R的情况,否则会陷入死循环

代码

#include<bits/stdc++.h>
#define ll long long
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
using namespace std;
const int maxn=200010;
const int INF=0x3f3f3f3f;
int tre[maxn<<2],lazy[maxn<<2];
int d[maxn],s[maxn];
int w[maxn],c[maxn],l[maxn],r[maxn],f[maxn];
int ans;
vector<int> g[maxn];
inline void pushup(int r)
{
	tre[r]=min(tre[r<<1],tre[r<<1|1]);
}
void build(int rt,int l,int r)
{
	lazy[rt]=0;
	if(l==r)
	{
		tre[rt]=f[l];
		return;
	}
	int mid=(l+r)>>1;
	build(lson);
	build(rson);
	pushup(rt);
}
void pushdown(int rt,int l,int r)
{
	if(lazy[rt]&&l!=r)
	{
		int a=lazy[rt];
		lazy[rt<<1]+=a;
		lazy[rt<<1|1]+=a;
		tre[rt<<1]+=a;
		tre[rt<<1|1]+=a;
		lazy[rt]=0;
	}
}
void update(int rt,int l,int r,int x,int y,int z)
{
	if(x>y) return;
	pushdown(rt,l,r);
	if(x<=l&&r<=y)
	{
		lazy[rt]+=z;
		tre[rt]+=z;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(lson,x,y,z);
	if(mid<y) update(rson,x,y,z);
	pushup(rt);
}
int query(int rt,int l,int r,int x,int y)
{
	//cout<<rt<<' '<<l<<' '<<r<<' '<<x<<' '<<y<<endl;
	if(x>y) return 0;pushdown(rt,l,r);
	int res=0x3f3f3f3f;
	if(x<=l&&r<=y) return tre[rt];
	int mid=(l+r)>>1;
	if(x<=mid) res=min(res,query(lson,x,y));
	if(mid<y) res=min(res,query(rson,x,y));
	return res;
}
int main()
{
	int n,k;
	scanf("%d %d",&n,&k);k++;
	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]);
	d[++n]=INF;;w[n]=INF;
	for(int i=1;i<=n;i++){
		l[i]=lower_bound(d+1,d+n+1,d[i]-s[i])-d;
		r[i]=lower_bound(d+1,d+n+1,d[i]+s[i])-d;
		if(d[r[i]]>d[i]+s[i]) r[i]--;
		g[r[i]].push_back(i);
	}
	int t=0;
	for(int i=1;i<=n;i++){
		f[i]=t+c[i];
		for(int j:g[i]) t+=w[j];
	}
	ans=f[n];
	if(k>1){
		for(int i=2;i<=k;i++){
			build(1,1,n);
			for(int j=1;j<=n;j++){
				f[j]=query(1,1,n,1,j-1)+c[j];
				for(int p:g[j]) update(1,1,n,1,l[p]-1,w[p]);
			}
			ans=min(ans,f[n]);
		}
	}
	printf("%d\n",ans);
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值