2020.10.03日常总结——二分算法(下)

本文介绍了如何运用二分搜索算法解决两类问题:一是找到最大间隔最小的选择方案,通过二分答案并使用单调队列优化求解;二是确定摧毁直线上法坛所需的最小法杖长度,利用二分法判断并采用尺取法求解覆盖范围。两种问题都展示了二分算法在处理优化问题时的有效性。
摘要由CSDN通过智能技术生成

在昨天的博客中,我们粗谈了一下二分和一些零碎算法的结合,今天我们讲讲二分和 OI 中另一类非常重要的算法——dp 的结合。

最小间隔 \color{green}{\texttt{最小间隔}} 最小间隔

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

  • 现有 n n n 个数 a 1 ⋯ n a_{1 \cdots n} a1n,你需要从中选取若干个(包括 1 1 1 个),要求如下:

      1. 选取的数和 ≤ m \leq m m m m m 为题目给定的数)。
      1. 最大间隔最小(间隔:选取数列中后一个数和前一个数在原数列中位置的差 − 1 -1 1,第一个数的间隔则为它在原数列位置 − 1 -1 1;最大间隔:所有间隔的最大值)。
  • 求可能的最小的最大间隔。

  • 1 ≤ n ≤ 5 × 1 0 4 , 1 ≤ m ≤ 1 × 1 0 9 1 \leq n \leq 5 \times 10^4,1 \leq m \leq 1 \times 10^9 1n5×104,1m1×109

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

最大值最小,一看就想到二分答案。

二分答案 mid \texttt{mid} mid,表示最大间隔为 mid \texttt{mid} mid,看看是否可行。

设计一个 dp:记 f i f_{i} fi 表示第 i i i 个数必选时最小的权值和。

我们有如下的转移:

f i = min ⁡ i − mid − 1 ≤ j ≤ i { f j } + a i f_{i}=\min\limits_{i-\texttt{mid}-1 \leq j \leq i}\left \{f_{j}\right \}+a_i fi=imid1jimin{fj}+ai

直接转移是 O ( n 2 ) O(n^2) O(n2) 的,必超时。仔细观察可以发现,我们可以用单调队列优化我们的 dp,于是 dp 时间复杂度降为 O ( n ) O(n) O(n)

总时间复杂度: O ( n × log ⁡ m ) O(n \times \log m) O(n×logm)

[code] \color{blue}{\texttt{[code]}} [code]

const int N=5e4+100;
int l,r,mid,ans,n,T;
int f[N],q[N],h,t,a[N];
inline bool check(int mid){
	h=1;t=1;q[1]=f[1]=0;//init
//	注意t必须初始化为1而不是0!!
	for(int i=1;i<=n;i++){
		while (h<=t&&q[h]+mid+1<i) ++h;
		f[i]=f[q[h]]+a[i];//转移方程 
		while (h<=t&&f[q[t]]>=f[i]) t--;
		q[++t]=i;//注意入队的是下标而非数 
	}
	for(int i=n-mid;i<=n;i++)
		if (f[i]<=T) return true;
	return false;
}
int main(){
	l=1;r=n=read();T=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	while (l<=r){
		mid=(l+r)>>1;
		if (check(mid)){
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	printf("%d",ans);
	return 0;
}

攻击法坛 \color{green}{\texttt{攻击法坛}} 攻击法坛

[Problem] \color{blue}{\texttt{[Problem]}} [Problem]

  • 一条直线上有 n n n 个法坛,分别位于位置 a 1 , a 2 , a 3 ⋯ a n a_1,a_2,a_3 \cdots a_n a1,a2,a3an,你需要把它们全部摧毁。
  • 你有两根法杖:一根可以摧毁连续 L L L 个位置上的所有法坛,能使用 R R R 次,另一根能摧毁连续 2 × L 2 \times L 2×L 个位置上的所有法坛,能使用 G G G 次。 L L L 是自己的指定的参数。
  • L L L 的最小取值使你能把 n n n 个法坛全部摧毁。
  • 1 ≤ n ≤ 2000 , 1 ≤ R , G , a i ≤ 1 × 1 0 9 ( 1 ≤ i ≤ n ) 1 \leq n \leq 2000,1 \leq R,G,a_{i} \leq 1 \times 10^9(1 \leq i \leq n) 1n2000,1R,G,ai1×109(1in)

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

首先明确一点:当 R + G ≥ n R+G\geq n R+Gn 时,答案为 1 1 1,因为我们可以对于每个法坛都用一次法杖。

明确这点有什么用?有。一特判,二把 R , G R,G R,G 的范围降到 1 ≤ R , G ≤ n 1\leq R,G \leq n 1R,Gn

答案明显有单调性(可二分性),考虑二分答案。

二分答案 mid \texttt{mid} mid 表示判断当 L = mid L=\texttt{mid} L=mid 时是否可行。

P i P_i Pi 表示在 a i a_i ai 位置使用一次第一根法杖最远可以覆盖到哪个法坛,同理 Q i Q_i Qi 表示在 a i a_i ai 位置使用一次第二根法杖最远可以覆盖到哪个法坛。对是否熟悉的同学一读这段话应该就可以知道:我们可以用尺取法在 O ( n ) O(n) O(n) 的时间复杂度内求出 P i , Q i P_i,Q_i Pi,Qi

特别地,为了方便处理,我们令 P n + 1 = Q n + 1 = n P_{n+1}=Q_{n+1}=n Pn+1=Qn+1=n

f i , j f_{i,j} fi,j 表示使用 i i i 次第一根法杖和 j j j 次第二根法杖最多可以覆盖到前几个法坛,则有转移方程:

f i , j = max ⁡ ( P f i − 1 , j + 1 , Q f i , j − 1 + 1 ) f_{i,j}=\max(P_{f_{i-1,j}+1},Q_{f_{i,j-1}+1}) fi,j=max(Pfi1,j+1,Qfi,j1+1)

转移方程相当的简单易懂。

初始对于 ∀ i , j \forall i,j i,j,都有 f i , j = 0 f_{i,j}=0 fi,j=0 。最后如果 f R , G = n f_{R,G}=n fR,G=n mid \texttt{mid} mid 可行,否则不可行。

时间复杂度: O ( R × G × log ⁡ a i ) O(R \times G \times \log a_{i}) O(R×G×logai)

[code] \color{blue}{\texttt{[code]}} [code]

int R,G,n,a[2010],l,r,ans,mid;
int f[2010][2010],P[2010],Q[2010];
inline void ckmax(int &a,int b){
	a=max(a,b);//让a取到a,b间较大值 
}
inline bool check(int mid){
	memset(f,0,sizeof(f));
	memset(P,0,sizeof(P));
	memset(Q,0,sizeof(Q));
	for(int i=1,j=1,k=1;i<=n;i++){
		while (j<=n&&a[j]-a[i]+1<=mid) j++;
		while (k<=n&&a[k]-a[i]+1<=2*mid) k++;
		P[i]=j-1;Q[i]=k-1;//注意这里的减1 
	}
	P[n+1]=Q[n+1]=n;//特判 
	for(int i=0;i<=R;i++)
		for(int j=0;j<=G;j++){
			if (i>0) ckmax(f[i][j],P[f[i-1][j]+1]);
			if (j>0) ckmax(f[i][j],Q[f[i][j-1]+1]);
		}
	return f[R][G]>=n;
}
int main(){
	scanf("%d%d%d",&n,&R,&G);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);//先要排序 
	if (R+G>=n){//足够各个位置都用 
		printf("1");return 0;
	}
	l=1;r=a[n]-a[1]+1;
	while (l<=r){
		mid=(l+r)>>1;
		if (check(mid)){
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值