Loading

P2605 [ZJOI2010]基站选址

P2605 [ZJOI2010]基站选址

浪费了洛谷28发提交,有必要写篇题解

首先这个是dp无疑了。

考虑设状态。最容易想到的应该是: \(dp[i][j]\) 表示考虑到第 \(i\) 个位置,建 \(j\) 个基站的最小代价,答案是 \(\min\{dp[n][i]\}(0\le i \le k)\)

但是发现不好转移,那么换成:\(dp[i][j]\) 表示考虑到第 \(i\) 个位置,钦定第 \(i\) 个位置建基站,前 \(i\) 个位置总共建了 \(j\) 个基站的最小代价。

这样在统计答案的时候要改成 \(dp[i][j]+f(i,n)(1\le i \le n,0\le j \le k)\)\(f(l,r)\) 表示在 \(l\) 建基站,\([l,r]\) 中间总共需要赔偿多少。

那么转移很显然了,\(dp[i][j]=\min\{dp[k][j]+g(k,i) \} (0\le j<i)\) ,\(g(l,r)\) 表示在 \(l,r\) 都建基站,\([l,r]\) 中间总共需要赔偿多少。

边界:\(dp[0][0]=0\)

\(O(n^2k)\) 解决了。

然后我发现我只有 \(20\) 分。说好 \(40\%\) 的数据 \(n\le 500\) 呢?数组开到 \(1000\) 就过了 #4\(30\) 分。

为啥不是 \(40\) 啊,#2 怎么 WA 了啊?!然后陷入了困境,开始疯狂提交

忽然发现,边界应该是 \(dp[1][i]=h(1,i)\),\(h(l,r)\) 表示在 \(r\) 建基站 \([l,r]\) 中间总共需要赔偿多少。

因为如果第一维从 \(1\) 开始转移的话,我们上面转移方程默认的是 \(j\) 左边覆盖不到的 \(i\) 一定覆盖不到,所以只用考虑 \([j,i]\) 之间产生的贡献即可。但是一开始没有基站,所以这个条件不成立,然后就挂了。那怎么还有30分

现在就有 \(40\) 了。

后记:后面优化 \(dp\) 的时候拿暴力对拍发现 \(40\) 分的程序统计答案错了,但是它有 \(40\) 分。。。

接下去考虑优化 \(dp\)

\(f,h\) 两个函数都可以二分预处理出来(或者你看了后面的处理可以不用二分)。

这个转移方程长得是一个区间最小值的形式,就是 \(g(l,r)\) 特别不爽,没法优化。

这种时候往往考虑直接拆 \(g\) 函数,拆成最本质的形式。

我们发现一段区间 \([D[k]-S[k],D[k]+S[k]]\) 不包括 \(i,j\) 时 ,\(g(i,j)+=W[k]\)

对于一个确定的左端点 \(j\) ,它所能产生的贡献时确定的,主要是右端点 \(i\) 在不断右移,而不断右移就是一个 单调 的东西。

这意味着一旦 \(i\) 往右移动到某个临界点,某一些 \(k\) 也无法被 \(j\) 覆盖的时候,\(W[k]\) 的贡献必然产生,而且这个贡献是针对某一段 \(j\) 产生的。

到现在,线段树很明显了吧!

首先二分预处理每一个位置 \(i\) ,能覆盖它的最靠左的端点 \(L[i]\) 和最靠右的端点 \(R[i]\)

对于每一个位置开 vector ,记录 \(R[j]=i\) 的区间编号 \(id\)

每次从左往右扫,先查询 \([1,i-1]\) 的最小值更新 \(dp\) 值,再把 \([1,L[id]-1]\) 里的贡献加上 \(W[id]\)

其实 \(dp\) 数组可以开一维,迭代跑 \(k\) 次即可。

空间 \(O(n)\) ,时间复杂度 \(O(nk\log n)\)

//Orz cyn2006
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define mkp(x,y) make_pair(x,y)
#define fi first
#define se second
#define pb(x) push_back(x)
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	return f?x:-x;
}
#define N 20005
#define K 105
#define T (N<<2)
#define inf 1000000005
int n,k,D[N],C[N],S[N],W[N],dp[N],ans,L[N],R[N],pre[N],suf[N];
vector<pair<int,int> >v1[N],v2[N];
int val[T],tag[T];
#define lc (p<<1)
#define rc (p<<1|1)
void pushup(int p){val[p]=min(val[lc],val[rc]);}
void build(int l=1,int r=n,int p=1){
	tag[p]=0;
	if(l==r)return val[p]=min(dp[l],inf),void();
	int mid=(l+r)>>1;
	build(l,mid,lc),build(mid+1,r,rc);
	pushup(p);
}
void pushdown(int p){
	if(tag[p]){
		val[lc]+=tag[p],val[rc]+=tag[p];
		tag[lc]+=tag[p],tag[rc]+=tag[p];
		tag[p]=0;
	}
}
void update(int ql,int qr,int k,int l=1,int r=n,int p=1){
	if(ql>qr)return;
	if(ql<=l&&r<=qr)return val[p]+=k,tag[p]+=k,void();
	pushdown(p);
	int mid=(l+r)>>1;
	if(ql<=mid)update(ql,qr,k,l,mid,lc);
	if(mid<qr)update(ql,qr,k,mid+1,r,rc);
	pushup(p);
}
int query(int ql,int qr,int l=1,int r=n,int p=1){
	if(ql>qr)return inf;
	if(ql<=l&&r<=qr)return val[p];
	pushdown(p);
	int mid=(l+r)>>1;
	if(qr<=mid)return query(ql,qr,l,mid,lc);
	if(mid<ql)return query(ql,qr,mid+1,r,rc);
	return min(query(ql,qr,l,mid,lc),query(ql,qr,mid+1,r,rc));
}
signed main(){
	n=read(),k=read();
	for(int i=2;i<=n;++i)D[i]=read();
	for(int i=1;i<=n;++i)C[i]=read();
	for(int i=1;i<=n;++i)S[i]=read();
	for(int i=1;i<=n;++i)W[i]=read();
	for(int i=1;i<=n;++i){
		L[i]=lower_bound(D+1,D+n+1,D[i]-S[i])-D;
		R[i]=upper_bound(D+1,D+n+1,D[i]+S[i])-D-1;
		v1[R[i]].pb(mkp(L[i],i)),v2[L[i]].pb(mkp(R[i],i));
	}
	for(int i=1;i<=n;++i){
		pre[i]+=pre[i-1];
		for(int j=0,sz=v1[i].size();j<sz;++j)pre[i+1]+=W[v1[i][j].se];
	}
	for(int i=n;i>=1;--i){
		suf[i]+=suf[i+1];
		for(int j=0,sz=v2[i].size();j<sz;++j)suf[i-1]+=W[v2[i][j].se];
	}
	memset(dp,0x3f,sizeof(dp));
	for(int i=1;i<=n;++i)ans+=W[i];
	if(!k)return printf("%d\n",ans),0;
	for(int i=1;i<=n;++i)dp[i]=pre[i]+C[i],ans=min(ans,dp[i]+suf[i]);
	for(int t=2;t<=k;++t){
		build();
		for(int i=1;i<=n;++i){
			dp[i]=query(1,i-1)+C[i],ans=min(ans,dp[i]+suf[i]);
			for(int j=0,sz=v1[i].size();j<sz;++j)update(1,v1[i][j].fi-1,W[v1[i][j].se]);
		}
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2020-11-05 09:15  zzctommy  阅读(113)  评论(0编辑  收藏  举报