P2605 [ZJOI2010] 基站选址 题解

P2605 [ZJOI2010] 基站选址 题解


题意分析

略……


思路分析

明显地,这是一道背包 DP。我们可以先简单地构思一下部分分:

设状态 \(f_{i,j}\) 表示前 \(i\) 个村庄中已经建立了 \(j\) 个基站,这 \(i\) 个村庄的总费用,转移方程:

\[f_{i,j} = \min_{q=j-1}^{i-1} (f_{q,j-1} + cost_{q,i}) + c_{i} \]

其中 \(cost_{q,i}\) 表示当第 \(q\) 个与第 \(i\) 个村庄建立基站,中间第 \(q+1 \sim i-1\) 这些村庄全部不建,所需的补偿。

为了方便,我们新增一个点,并设为一定选入,放在最后,这样可以直接从它统计答案,不过 \(k\) 也要记得加一。

当我们将上述式子直接强行动态套入,发现空间是 \(O(n \times (n+k))\),时间为 \(O(n^4)\)

似乎还拿不到分数,尝试优化:

最明显的是 \(cost_{q,i}\) 可以通过 \(O(n^3)\) 暴力或 \(O(n^2\log_2{n})\) 扫描线预处理,那么时间复杂度来到了 \(O(n^2k \sim n^3)\)。不过为了不浪费精力与时间,\(O(n^2\log_2{n})\) 的扫描线预处理可以直接略过。

另外,一个很套路的隐藏滚动数组优化背包 DP 空间可以将 \(f_{i,j}\) 压为 \(f_{i}\)\(j\) 放在外层循环,可以拿到 \(50\%\) 的部分分。

我们发现预处理中的边界是固定的,且有一个明显的区间性,那么我们可以尝试数据结构优化。

先把每一个会影响村庄 \(i\) 的基站范围设为 \(l_i,r_i\),然后在 DP 到 \(r_i\) 时对区间 \(j-1 \sim l_i - 1\) 加一个 \(w_i\),这可以用链式前向星或 std::vector 存储并调用。然后在更新 \(f_i\) 时,直接就可以对区间 \(j-1,i-1\) 查询最小值。

区间加,区间查询都可以使用线段树维护,然后就可以解决了。


CODE

\(50\%\) 部分分

时间复杂度:\(O(n^3)\),空间复杂度:\(O(n^2)\)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define max(a,b) ((a)<(b)?(b):(a))
#define min(a,b) ((a)>(b)?(b):(a))
#define tomax(a,b) ((a)=max((a),(b)))
#define tomin(a,b) ((a)=min((a),(b)))
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define EDGE(g,i,u,x) for(register int (i)=(g).h[(u)],(x)=(g).v[(i)];(i);(i)=(g).nxt[(i)],(x)=(g).v[(i)])
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=2e3+10,K=1e2+10;
int n,k,ans;
int d[N],c[N],s[N],w[N],l[N],r[N],f[N];
int sum[N][N];
signed main() {
	cin>>n>>k;
	FOR(i,2,n)cin>>d[i];
	FOR(i,1,n)cin>>c[i];
	FOR(i,1,n)cin>>s[i];
	FOR(i,1,n)cin>>w[i],ans+=w[i];
	FOR(i,1,n)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;
	l[n+1]=r[n+1]=++n,w[n]=INF,RCL(f,INF,f,1),f[0]=0;
	FOR(i,1,n)FOR(L,0,l[i]-1)FOR(R,r[i]+1,n)sum[L][R]+=w[i];
	FOR(i,1,k+1) {
		DOR(j,n,i)FOR(q,i-1,j-1)tomin(f[j],f[q]+sum[q][j]+c[j]);
		tomin(ans,f[n]);
	}
	cout<<ans<<endl;
	return 0;
}

\(100\%\) 满分

时间复杂度:\(O(nk\log_2{n})\),空间复杂度:\(O(n)\)

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define tomax(a,b) ((a)=max((a),(b)))
#define tomin(a,b) ((a)=min((a),(b)))
#define RCL(a,b,c,d) memset((a),(b),sizeof(c)*(d))
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define DOR(i,a,b) for(register int i=(a);i>=(b);--i)
#define EDGE(g,i,u,x) for(register int (i)=(g).h[(u)],(x)=(g).v[(i)];(i);(i)=(g).nxt[(i)],(x)=(g).v[(i)])
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
constexpr int N=2e4+10,K=1e2+10;
int n,k,ans;
int d[N],c[N],s[N],w[N],l[N],r[N],f[N];
struct CFS{
	int tot,h[N],v[N],w[N],nxt[N];
	int &operator[](int i){
		return w[i];
	}
	void att(int U,int V,int W){
		v[++tot]=V,nxt[tot]=h[U],h[U]=tot,w[tot]=W;
	}
}g;
struct SEG{
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (tr[p].l+tr[p].r>>1)
	struct node{
		int l,r,val,tag;
	}tr[N<<2];
	void Up(int p){
		tr[p].val=min(tr[ls].val,tr[rs].val);
	}
	void down(int p,int tag){
		tr[p].val+=tag,tr[p].tag+=tag;
	}
	void Down(int p){
		if(tr[p].tag)down(ls,tr[p].tag),down(rs,tr[p].tag),tr[p].tag=0;
	}
	void Build(int p,int l,int r){
		tr[p]={l,r,0,0};
		if(l==r)return tr[p].val=f[l],void();
		Build(ls,l,mid),Build(rs,mid+1,r),Up(p);
	}
	void Plus(int p,int l,int r,int d){
		if(l<=tr[p].l&&tr[p].r<=r)return down(p,d);
		Down(p);
		if(l<=mid)Plus(ls,l,r,d);
		if(mid<r)Plus(rs,l,r,d);
		Up(p);
	}
	int Query(int p,int l,int r){
		if(l<=tr[p].l&&tr[p].r<=r)return tr[p].val;
		return Down(p),min(l<=mid?Query(ls,l,r):INF,mid<r?Query(rs,l,r):INF);
	}
#undef ls
#undef rs
#undef mid
}seg;
signed main(){
	cin>>n>>k;
	FOR(i,2,n)cin>>d[i];
	FOR(i,1,n)cin>>c[i];
	FOR(i,1,n)cin>>s[i];
	FOR(i,1,n)cin>>w[i],ans+=w[i];
	FOR(i,1,n)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;
	++n,++k,l[n]=r[n]=n,d[n]=w[n]=INF,c[n]=s[n]=0;
	FOR(i,1,n)g.att(r[i],l[i],w[i]);
	RCL(f,INF,f,1),f[0]=0;
	FOR(i,1,k){
		seg.Build(1,i-1,n);
		FOR(j,1,n){
			if(j>=i)f[j]=seg.Query(1,i-1,j-1)+c[j];
			EDGE(g,z,j,q)if(q>=i)seg.Plus(1,i-1,q-1,g[z]);
		}
		tomin(ans,f[n]);
	}
	cout<<ans<<endl;
	return 0;
}

总结

要通过题目特征联想到合适的数据结构,并合理运用数据结构的动态性来维护一些问题。


后记

据说这题还可以用 WQS 二分做到更优,但是这里不作探究。

posted @ 2024-07-04 20:56  Add_Catalyst  阅读(13)  评论(0)    收藏  举报