Loading

P2605 [ZJOI2010]基站选址

题意

数轴上有 \(n\) 个村庄,现在可以建立不超过 \(k\) 个基站,对于一个村庄,如果在不超过 \(S_i\) 的范围内有基站,那么它就是被覆盖的。在村庄 \(i\) 建立一个基站需要花费 \(C_i\)。如果一个村庄 \(i\) 没有被覆盖,那么就需要付出 \(W_i\) 的代价,求最小代价和。

Solution

要付的代价越来越多,我该怎么办。。。

应该不难想到 \(dp\) 的一个做法,就是枚举最后一个基站放的位置,令 \(dp_{i,j}\) 表示前 \(j\) 个村庄中放 \(i\) 个基站,并且最后一个放基站的最小代价和。那我们转移需要知道上一个决策点的位置 \(k\) 和当前决策点之间未被覆盖的村庄代价之和。假设对于村庄 \(i\),我们令能覆盖到它的最左边村庄为 \(L_i\),最右边的是 \(R_i\),这二者都可以预处理。那么假设我们在转移 \(i\) 的时候,就已经处理好一个数组 \(cst\)\(cst_j\) 表示在区间 \([j,i]\) 中,如果 \(j,i\) 放基站,那么其中不能被覆盖到的点的权值和。然后全局开一个堆,存每个点的 \(D_i+S_i\),每次取出最小的,如果这个点的 \(R_i<i+1\),就用它的权值更新区间 \([1,L_i-1]\)\(cst\) 值。

接下来就是简单的转移了:

\[dp_{i,j}=\min\{dp_{i-1,k}+cst_k\}+C_j \]

诶,这东西不是能线段树优化吗。我们在每个节点 \(k\)\(dp_{i-1,k}\)。然后每次更新的时候也在线段树上实现区间加。

Code

// Problem: 
//     P2605 [ZJOI2010]基站选址
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2605
// Memory Limit: 125 MB
// Time Limit: 1000 ms

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define all(a) a.begin(),a.end()
#define siz(a) (int)a.size()
#define pb emplace_back
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
#define int long long
using namespace std;
const int MAXN=2e4+10;
struct Tree{int l,r,mn,inc;}tr[MAXN<<2];
#define ls i<<1
#define rs i<<1|1
int ID,dp[110][MAXN];
void pushup(int i){tr[i].mn=min(tr[ls].mn,tr[rs].mn);}
void build(int i,int l,int r){
	tr[i].l=l;tr[i].r=r;tr[i].inc=0;
	if(l==r){tr[i].mn=dp[ID-1][l];return;}
	int mid=(l+r)>>1;build(ls,l,mid),build(rs,mid+1,r);
	pushup(i);
}
void add(int i,int v){tr[i].mn+=v;tr[i].inc+=v;}
void pushdown(int i){
	if(!tr[i].inc) return;
	add(ls,tr[i].inc);add(rs,tr[i].inc);
	tr[i].inc=0;
}
void upd(int i,int l,int r,int v){
	if(l>r) return;
	if(tr[i].l==l&&tr[i].r==r){add(i,v);return;}
	pushdown(i);int mid=(tr[i].l+tr[i].r)>>1;
	if(r<=mid) upd(ls,l,r,v);else if(l>mid) upd(rs,l,r,v);
	else upd(ls,l,mid,v),upd(rs,mid+1,r,v);pushup(i);
}
int ask(int i,int l,int r){
	if(l>r) return 0;
	if(tr[i].l==l&&tr[i].r==r) return tr[i].mn;
	pushdown(i);int mid=(tr[i].l+tr[i].r)>>1;
	if(r<=mid) return ask(ls,l,r);else if(l>mid) return ask(rs,l,r);
	else return min(ask(ls,l,mid),ask(rs,mid+1,r));
}
int L[MAXN],R[MAXN],D[MAXN],C[MAXN],S[MAXN],W[MAXN],suf[MAXN];
struct info{
	int i,val;
	bool friend operator<(info a,info b){
		return a.val>b.val;
	}
};
priority_queue<info> q;
void solve(){
	int n,k;cin>>n>>k;
	rep(i,2,n) cin>>D[i];
	rep(i,1,n) cin>>C[i];
	rep(i,1,n) cin>>S[i];
	rep(i,1,n) cin>>W[i];
	rep(i,1,n){
		L[i]=lower_bound(D+1,D+1+i,D[i]-S[i])-D;
		R[i]=upper_bound(D+i,D+n+1,D[i]+S[i])-D-1;
	}
	while(!q.empty()) q.pop();
	int cur=0;
	rep(i,1,n){
		while(!q.empty()&&R[q.top().i]<i){
			cur+=W[q.top().i];q.pop();
		}
		dp[1][i]=cur+C[i];
		q.push(info{i,D[i]+S[i]});
	}
	for(ID=2;ID<=k;ID++){
		build(1,1,n);
		while(!q.empty()) q.pop();
		rep(i,1,n){
			while(!q.empty()&&R[q.top().i]<i){
				upd(1,1,L[q.top().i]-1,W[q.top().i]);
				q.pop();
			}
			dp[ID][i]=ask(1,1,i-1)+C[i];
			q.push(info{i,D[i]+S[i]});
		}
	}
	while(!q.empty()) q.pop();
	int ans=INF,tmp=0;cur=0;
	per(i,n,1){
		tmp+=W[i];
		while(!q.empty()&&L[q.top().i]>i){
			cur+=W[q.top().i];q.pop();
		}
		int now=INF;
		rep(id,1,k) now=min(now,dp[id][i]);
		ans=min(ans,now+cur);
		q.push(info{i,S[i]-D[i]});
	}
	cout<<min(ans,tmp)<<'\n';
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
//	int T;for(cin>>T;T--;)
		solve();
	return 0;
}
posted @ 2022-10-31 10:07  ZCETHAN  阅读(38)  评论(0编辑  收藏  举报