P2605 [ZJOI2010] 基站选址 题解
P2605 [ZJOI2010] 基站选址 题解
题意分析
略……
思路分析
明显地,这是一道背包 DP。我们可以先简单地构思一下部分分:
设状态 \(f_{i,j}\) 表示前 \(i\) 个村庄中已经建立了 \(j\) 个基站,这 \(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 二分做到更优,但是这里不作探究。

浙公网安备 33010602011771号