P2605 [ZJOI2010] 基站选址

发布时间 2024-01-12 20:28:47作者: blueparrot

题意:有 \(n\) 个点,告诉你每个点距离第一个点的距离,需要在 \(n\) 个点中选择出 \(K\) 个关键点,选这个点作为关键点代价为 \(W_i\)。现在还有代价, \(S_i\) 表示如果距离 \(i\)\(S_i\) 以内的点存在一个关键点,那么这个点就被覆盖了,不产生代价。选完 \(K\) 个关键点之后,如果存在第 \(i\) 个点没被覆盖就会产生 \(W_i\) 的额外代价。求最小化代价。

题看错了一次( \(S_i\) 那里 )。这种题还要多练。

首先 \(dp[i][j]\) 表示前 \(i\) 个点选了 \(j\) 个点作为关键点,强制选 \(i\) 的最小代价。

转移随便,发现有个 \(cost(k+1,i-1 )\) 这个代价函数,表示的是这段区间内没被覆盖的点的总额外代价。记得滚掉一维。直觉判断 \(cost\) 函数不能硬维护。首先发现每个点所代表的区间左右延伸的最后一个点能够处理,那么我 DP 的时候选择的上一个点在 \([1,l-1]\) 的话显然就需要加上 \(W_i\) 的代价,所以只需要做一个区间加就行了,然后求 \(dp[i]\) 就是前缀 \(\min\) 。所以说我先转移 \(dp\) ,然后我做一个扫描,扫到这个点,我就判断它是哪些区间的右端点,并把这些区间的 \([1,l-1]\) 区间加 \(W_i\)

值得注意的是,我们最后转移 \(dp[n]\) 的时候,我们是强制选 \(n\) ,但是可以不选,所以搞个虚拟点,统计答案就行。

#include<bits/stdc++.h>
#define int long long 
#define MAXN 30005
const int inf=1e18;
int gi(){
	char c;int x=0,f=0;
	while(!isdigit(c=getchar()))f|=(c=='-');
	while(isdigit(c))x=(x*10)+(c^48),c=getchar();
	return f?-x:x;
}
template<class T>void cxk(T&a,T b){a=a>b?a:b;}
template<class T>void cnk(T&a,T b){a=a<b?a:b;}
int n,K,d[MAXN],c[MAXN],s[MAXN],w[MAXN],dp[MAXN],l[MAXN];
std::vector<int>g[MAXN];
namespace segtree{
#define ls x<<1
#define rs x<<1|1
#define mid ((l+r)>>1)
	int sum[MAXN<<2],tag[MAXN<<2];
	void up(int x){sum[x]=std::min(sum[ls],sum[rs]);}
	void addtag(int x,int l,int r,int w){sum[x]+=w,tag[x]+=w;}
	void down(int x,int l,int r){
		if(!tag[x])return;
		addtag(ls,l,mid,tag[x]),addtag(rs,mid+1,r,tag[x]);
		tag[x]=0;
	}
	void build(int x,int l,int r){
		tag[x]=0;
		if(l==r)return sum[x]=dp[l],void();
		build(ls,l,mid),build(rs,mid+1,r);
		up(x);
	}
	void upd(int x,int l,int r,int L,int R,int w){
		if(l>R||r<L)return;
		if(L<=l&&r<=R)return addtag(x,l,r,w);
		down(x,l,r);
		upd(ls,l,mid,L,R,w),upd(rs,mid+1,r,L,R,w);
		up(x);
	}
	int query(int x,int l,int r,int L,int R){
		if(l>R||r<L)return inf;
		if(L<=l&&r<=R)return sum[x];
		down(x,l,r);
		return std::min(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
	}
#undef ls
#undef rs
#undef mid
}
using namespace segtree;
int find(int x,int i){
	int lt=0,rt=n+1,pos=i;
	while(lt+1<rt){
		int mid=(lt+rt)>>1;
		if(x<=d[mid])rt=mid,pos=mid;
		else lt=mid;
	}
	return pos;
}
int find2(int x,int i){
	int lt=0,rt=n+1,pos=i;
	while(lt+1<rt){
		int mid=(lt+rt)>>1;
		if(d[mid]<=x)lt=mid,pos=mid;
		else rt=mid;
	}
	return pos;
}
void DP(){
	int ans=inf,os=0;
	for(int i=1;i<=n;i++){
		dp[i]=c[i];
		for(int j=1;j<i;j++){
			if(d[j]-s[j]<=d[i]&&d[i]<=d[j]+s[j])continue;
			dp[i]+=w[j];
		}
	}
	ans=dp[n];
	for(int k=2;k<=K;k++){
		build(1,1,n);
		for(int i=1;i<=n;i++){
			dp[i]=query(1,1,n,1,i-1)+c[i];if(i==1)dp[1]=c[i];
			for(int v:g[i])if(v>=2)upd(1,1,n,1,l[v]-1,w[v]);
		}
		ans=std::min(ans,dp[n]);
	}
	printf("%lld\n",ans);
}
signed main(){
	n=gi(),K=gi();
	for(int i=2;i<=n;i++)d[i]=gi(); //距离
	for(int i=1;i<=n;i++)c[i]=gi(); //建立基站费用
	for(int i=1;i<=n;i++)s[i]=gi(); //范围
	for(int i=1;i<=n;i++)w[i]=gi(); //补偿费用
	++n,++K,d[n]=w[n]=inf,c[n]=0;
	for(int i=1;i<=n;i++)g[find2(d[i]+s[i],i)].emplace_back(i),l[i]=find(d[i]-s[i],i);
	DP();
	return 0;
}