笔记 基础算法

CF547B - Description

给你个长度为 \(n\) 的序列 \(a\),对于每个 \(1\le k\le n\),有 \(n-k+1\) 个中所有长度为 \(k\) 的子串,你需要求出这 \(n-k+1\) 个子串的区间最小值的最大值,即下面式子的值:

\[\max_{l=1}^{n-k+1} \{\ \min_{i=l}^{l+k-1} a_i\ \} \]

\(1\le n\le 2\times 10^5\)

CF547B - Solution

首先考虑转化这个问题。假设有 \(l\le i\le r\),其中 \(a_i\) 是区间 \(\left [ l,r\right ]\) 的最小值,那么 \(a_i\) 一定具有这样一个性质:

  • 对于所有 \(j<i\),若 \(a_i<a_j\),则有 \(j<l\)
  • 对于所有 \(j>i\),若 \(a_i<a_j\),则有 \(j>r\)

针对这个性质,我们可以在 \(k\) 为定值时将问题转化为一个判定性问题。假设我们目前求出了 \(lft_i\)\(rgt_i\) 来分别表示在 \(i\) 前面第一个小于 \(a_i\) 的数的位置和在 \(i\) 后面的第一个小于 \(a_i\) 的数的位置,那么 \(a_i\) 可以是 $\left [ lft_i+1,rgt_i-1\right ] $ 中任意一个子区间的区间最小值,因此 \(a_i\) 会对所有 \(k\le rgt_i-lft_i-1\) 的答案做出贡献(下面将记作 \(b_i\))。这里的 \(rgt\)\(lft\) 我们可以使用单调栈 \(O(n)\) 求出,而我们注意到对所有元素从大到小排序,第一个扫到的 \(b_i\) 一定就是最大的,后面不会再被更新,于是我们将 \((b_i,a_i)\) 存为二元组后对 \(a_i\) 从大到小排序。排完序扫一遍,如果每次从上一个更新后的 \(b_{lst}+1\) 开始扫,扫到 \(b_i\),中间所有答案都赋值为 \(a_i\)。因为所有位置都只会被扫到一次,所以时间复杂度为 \(O(n)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,a[200005],lft[200005],rgt[200005],ava[200005],ans[200005],lst;
stack<int>q,qq;
struct node{
	int val,av;
}e[200005];
inline bool cmp(node x,node y){
	if(x.val==y.val){
		return x.av>y.av;
	}
	return x.val>y.val;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		while(!q.empty()&&a[q.top()]>a[i]){
			rgt[q.top()]=i;
			q.pop();
		}
		q.push(i);
	}
	for(int i=n;i>=1;i--){
		while(!qq.empty()&&a[qq.top()]>a[i]){
			lft[qq.top()]=i;
			qq.pop();
		}
		qq.push(i);
	}
	for(int i=1;i<=n;i++){
		if(rgt[i]==0){
			rgt[i]=n+1;
		}
		ava[i]=rgt[i]-lft[i]-1;
		e[i].val=a[i];
		e[i].av=ava[i];
	}
	sort(e+1,e+1+n,cmp);
	for(int i=1;i<=n;i++){
		int val=e[i].val;
		for(int j=e[lst].av+1;j<=e[i].av;j++){
			lst=i;
			ans[j]=val;
		}
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}

ABC352D - Description

给你一个 \(n\) 的排列 \(a\),让你选出一个长度为 \(k\)\(a\) 的子序列 \(b=\left [ a_{p_1},a_{p_2},\cdots ,a_{p_k} \right ]\),使得 \(\min b_i +k-1=\max b_i\) 的同时控制 \(p_k-p_1\) 最小,求出这个最小值。

\(1\le n,k\le 2\times 10^5\)

ABC352D - Solution

注意到 \(a\) 数组本身就是排列,而要求选出排列就等于直接截取 \(a\) 的子串,这省去了中间的很多过程。先求出 \(c_{a_i}=i\),可以发现 \(c\)\(a\) 的一个置换,因此显然不影响答案。要让 \(b\) 数组构成一个排列 \(\{ a,a+1,\cdots ,a+k-1\}\),就是在 \(c\) 中选出一个长度为 \(k\) 的子串。而 \(p_k\) 就是这个子串中的最大 \(c_i\)\(p_1\) 就是最小 \(c_i\)。现在,答案就变成了下面式子的值:

\[\min_{i=1}^{n-k+1} (\max_{j=i}^{i+k-1} c_i - \min_{j=i}^{i+k-1} c_i) \]

单调队列扫描可以做到 \(O(n)\) 预处理。

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,k,a[200005],c[200005],minn[200005],maxx[200005],ans=INT_MAX;
deque<int>q;
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		c[a[i]]=i;
	}
//	for(int i=1;i<=n;i++){
//		cout<<c[i]<<" ";
//	}
//	cout<<endl;
	for(int i=1;i<=n;i++){
		while(!q.empty()&&i-q.front()>=k){
			q.pop_front();
		}
		while(!q.empty()&&c[q.back()]>c[i]){
			q.pop_back();
		}
		q.push_back(i);
		minn[i]=c[q.front()];
	}
	while(!q.empty()){
		q.pop_back();
	}
	for(int i=1;i<=n;i++){
		while(!q.empty()&&i-q.front()>=k){
			q.pop_front();
		}
		while(!q.empty()&&c[q.back()]<c[i]){
			q.pop_back();
		}
		q.push_back(i);
		maxx[i]=c[q.front()];
	}
	for(int i=1;i<=n-k+1;i++){
		int x=i+k-1;
		ans=min(ans,maxx[x]-minn[x]);
	}
	cout<<ans<<endl;
	return 0;
}

CF1407D - Description

\(n\) 栋楼,每栋楼有高度 \(h_i\),对于第 \(i\) 栋楼和第 \(j\) 栋楼,如果 \((i,j)\) 满足以下三个条件中的任意一个,我们认为可以从第 \(i\) 栋楼跳到第 \(j\) 栋楼:

  • \(i+1=j\)
  • \(\max(h_{i+1},h_{i+2},\cdots ,h_{j-1})<\min(h_i,h_j)\)
  • \(\min(h_{i+1},h_{i+2},\cdots ,h_{j-1})>\max (h_i,h_j)\)

问你从第 \(1\) 栋楼开始,最少跳几次能跳到第 \(n\) 栋楼?

\(2\le n\le 3\times 10^5\)

CF1407D - Solution

我们设计一个 \(f_i\) 表示从 \(1\) 走到 \(i\) 的最少步数。

对于限制 \(1\),容易得到 \(f_i=f_{i-1}+1\)

限制 \(2\) 就是说对于所有 \(i<k<j\)\(i\) 都是 \(k\) 左侧第一栋比 \(h_k\) 高的楼,\(j\) 都是 \(k\) 右侧第一栋比 \(h_k\) 高的楼。这种左边/右边第一个大于/小于的题很容易想到单调栈求出 \(lft\)\(rgt\)

我们从 \(i\) 往前找,遍历每一个 \(h_j<h_i\) 的点,如果 \(j\) 满足 \(\max\{ h_{j+1},\cdots ,h_{i-1} \}<h_j\),就可以从 \(j\) 跳到 \(i\)。我们发现这个条件就等于在讲对于 \(i\) 更新单调栈时会弹出 \(j\),于是维护单调栈的时候顺手计算就行。一个细节是如果 \(h_j=h_i\),是无法从 \(j\) 跳到 \(i\) 的,注意特判。

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,h[300005];
stack<int>q,qq;
long long dp[300005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
		dp[i]=INT_MAX;
	}
	dp[1]=0;
	q.push(1);
	qq.push(1);
	for(int i=2;i<=n;i++){
		int lst=INT_MAX;
		while(!q.empty()&&h[q.top()]>=h[i]){
			dp[i]=min(dp[i],dp[q.top()]+1);
			lst=h[q.top()];
			q.pop();
		}
		if(!q.empty()&&lst!=h[i]){
			dp[i]=min(dp[i],dp[q.top()]+1);
		}
		q.push(i);
		lst=INT_MAX;
		while(!qq.empty()&&h[qq.top()]<=h[i]){
			dp[i]=min(dp[i],dp[qq.top()]+1);
			lst=h[qq.top()];
			qq.pop();
		}
		if(!qq.empty()&&lst!=h[i]){
			dp[i]=min(dp[i],dp[qq.top()]+1);
		}
		qq.push(i);
	}
	cout<<dp[n]<<endl;
	return 0;
}

P1080 - Description

\(n+1\) 个具备 \(a\)\(b\) 两个数值的人(第 \(0\) 个人不参与计算,但是永远在所有人前面),对所有 \(1\le i\le n\),定义 \(f(i)\)

\[f(i)=\left \lfloor \frac{\prod_{j=0}^{i-1} a_j }{b_i}\right \rfloor \]

现在让你重新排列队伍,使最大的 \(f(i)\) 最小。(\(1\le i\le n\)

\(1\le n\le 1000,1\le a_i,b_i\le 10^4\)

P1080 - Solution

像这种存在前后关系,并且操作满足交换律的题目我们应当警惕。这告诉我们这种贪心可以使用临项交换解决。具体地,我们考虑相邻的两项 \(i\)\(i+1\),有:

\[f(i)=\left \lfloor \frac{a_0a_1\cdots a_{i-1}}{b_i}\right \rfloor \]

\[f(i+1)=\left \lfloor \frac{a_0a_1\cdots a_{i}}{b_{i+1}}\right \rfloor \]

我们考虑交换 \(i\)\(i+1\) 对答案的影响。

\[f(i)'=\left \lfloor \frac{a_0a_1\cdots a_{i-1}a_{i+1}}{b_i}\right \rfloor \]

\[f(i+1)'=\left \lfloor \frac{a_0a_1\cdots a_{i-1}}{b_{i+1}}\right \rfloor \]

如果 \(\arg \max f(i)\) 不是 \(i\)\(i+1\),临项交换前后是对答案没有影响的;如果是 \(i\)\(i+1\),我们发现交换操作就等同于给 \(f(i)\) 乘上 \(a_{i+1}\),给 \(f(i+1)\) 除以 \(a_i\)。如果 \(i\) 交换前更优,那么:

\[\left \lfloor \frac{a_0a_1\cdots a_{i}}{b_{i+1}}\right \rfloor<\left \lfloor \frac{a_0a_1\cdots a_{i-1}}{b_i}\right \rfloor<\left \lfloor \frac{a_0a_1\cdots a_{i-1}a_{i+1}}{b_i}\right \rfloor \]

也就是说:

\[\frac{a_i}{b_{i+1}}<\frac{a_{i+1}}{b_i} \]

化简一下,我们得到了 \(a_ib_i<a_{i+1}b_{i+1}\) 的结论。也就是说,如果 \(a_ib_i<a_{i+1}b_{i+1}\),那么交换后一定是不优的。于是我们按照 \(a_ib_i\) 从小到大排序,扫描一遍得出答案即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
long long n,ans;
struct node{
	int a,b;
}e[1005];
inline bool cmp(node x,node y){
	return x.a*x.b<y.a*y.b;
}
signed main(){
	cin>>n;
	for(int i=0;i<=n;i++){
		cin>>e[i].a>>e[i].b;
	}
	sort(e+1,e+1+n,cmp);
	int fac=e[0].a;
	for(int i=1;i<=n;i++){
		ans=max(ans,fac/e[i].b);
		fac*=e[i].a;
	}
	cout<<ans<<endl;
	return 0;
}
posted @ 2025-10-08 16:00  Creativexz  阅读(9)  评论(0)    收藏  举报