题解 P9691 [GDCPC2023] Base Station Construction

【洛谷专栏】

很好的单调队列优化 DP 练习题。

题意

\(i\) 个点排成一排,从左到右编号依次为 \(1 \sim n\)。选择第 \(i\) 个点的代价为 \(a_i\)

\(m\) 个要求,第 \(i\) 个要求为在 \([l_i,r_i]\) 中至少要选择一个节点。

请你找到代价最小的选择方案使得能够满足所有要求,并给出最小的代价。

分析

因为每个点的代价不同,所以不能直接贪心做区间选点问题,但是可以利用类似的思路。

\(f_i\) 表示前 \(i\) 个点中,第 \(i\) 个点必选的最小代价。

考虑转移,设转移决策点为 \(j\),那么必然要满足的条件是对于所有右端点小于 \(i\) 的区间,\(j\) 不能小于这些区间的左端点,否则这个区间就会被漏选(因为后面的决策点也选择不了这些漏选的区间)。

\(x_i\) 表示右端点小于 \(i\) 的区间左端点最大值。

然后就可以写出状态转移方程:

\[f_i = a_i + \min\limits_{j = x_i}^{i-1}f_j \]

具体实现就将所有区间按右端点从小到大排序,双指针维护最大值。因为这个决策点是单调递增的,所以可以使用单调队列优化。

最终的答案也即:

\[\min\limits_{j = x_n}^n f_j \]

时间复杂度 \(O(m \log m)\),瓶颈在于区间排序,动态规划部分的时间复杂度为 \(O(n+m)\)

代码

//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 500005
using namespace std;
typedef long long LL;
int n,m,a[MAXN];
struct SEG{
	int l,r;
	bool operator < (const SEG&B)const{return r<B.r;}//将区间按右端点从小到大排序。
}e[MAXN];
LL f[MAXN];
int q[MAXN],h,t;
void solve(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&e[i].l,&e[i].r);
	sort(e+1,e+m+1);
	int l=0;
	h=1,t=0,q[++t]=0;
	for(int i=1,j=1;i<=n;i++){
		f[i]=f[q[h]]+a[i];
		while(h<=t&&f[q[t]]>f[i]) --t;//弹出不优的决策。
		q[++t]=i;
		for(;j<=m&&e[j].r<=i;j++) l=max(l,e[j].l);//双指针维护最大值。
		while(h<=t&&q[h]<l) ++h;//排除过时决策。
	}
	printf("%lld\n",f[q[h]]);
}
int main(){
    int T;scanf("%d",&T);
    while(T--) solve();
    return 0;
}
posted @ 2025-02-09 20:39  Chen_Jinhui  阅读(77)  评论(0)    收藏  举报