线性优化策略复习

前言:教练突然把以前洛谷网校得东西翻出来,让我们补题。大多数题目都很水。这里放一些题目上来。

一些过水的题这里就不放了。

P2671 [NOIP2015 普及组] 求和

一道数学题。

首先由题意得\(2y=x+z\)

我们观察一下这个柿子,可以发现\(x,z\)必须要同奇同偶才能满足条件,那么我们就可以考虑枚举\(y\),再利用奇偶性,这里我们按奇偶和颜色分组,再推柿子:

记一个分组中数为\(x_i\),下标为\(y_i\)

则答案为:

\[\sum \limits_{i=1}^k ({x_i \times\sum y_i}) \]

先预处理\(y_i\)即可。

P4147 玉蟾宫

悬线法版题。

悬线法思路:悬线的定义,就是一条竖线,这条竖线要满足上端点在整个矩形上边界或者是一个障碍点。然后以这条悬线进行左右移动,直到移至障碍点或者是矩阵边界,进而确定这条悬线所在的极大矩阵。

其实本质上就是预处理。

用数组l,r记录某点向左和向右能到达的最远点的纵坐标。 用数组up记录某点向上能到达的最远距离。

最后枚举每个点求答案即可。

P2216 [HAOI2007]理想的正方形

一道水蓝。只要暴力地做4遍单调队列就行,只不过码量稍微有点,题解里似乎有更方便地方法。

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
	int f=1,w=0;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')	f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		w=(w<<1)+(w<<3)+(c^48);
		c=getchar();
	}
	return f*w;
}
int head,tail;
int a,b,n;
int s[1010][1110];
int q[1010];
int f[1010][1010];
int g[1010][1010];
int x[1010][1010];
int y[1010][1010];
void init1(){
	for(int i=1;i<=a;i++) {
		head=tail=q[1]=1;
		for(int j=2;j<=b;j++) {
			while(head<=tail&&s[i][q[tail]]<=s[i][j]) tail --;
			q[++tail] = j;
			while(head<=tail&&q[head]<=j-n) head ++;
			if(j >= n) f[i][j-n+1]=s[i][q[head]];
		}
	}
}
void init2(){
	memset(q,0,sizeof(q));
	for(int i=1;i<=a;i++) {
		head=tail=q[1]=1;
		for(int j=2;j<=b;j++) {
			while(head<=tail&&s[i][q[tail]]>=s[i][j]) tail --;
			q[++tail] = j;
			while(head<=tail&&q[head]<=j-n) head ++;
			if(j>=n) g[i][j-n+1] = s[i][q[head]];
		}
	}
}
void init3(){
	memset(q,0,sizeof(q));
	for(int i=1;i<=b-n+1;i++){
		head=tail=q[1]=1;
		for(int j=1;j<=a;j++){
			while(head<=tail&&f[q[tail]][i]<=f[j][i])	tail--;
			q[++tail]=j;
			while(head<=tail&&q[head]<=j-n)	head++;
			if(j>=n)	x[j-n+1][i]=f[q[head]][i];
		}
	}
}
void init4(){
	memset(q,0,sizeof(q));
		for(int i=1;i<=b-n+1;i++){
		head=tail=q[1]=1;
		for(int j=1;j<=a;j++){
			while(head<=tail&&g[q[tail]][i]>=g[j][i])	tail--;
			q[++tail]=j;
			while(head<=tail&&q[head]<=j-n)	head++;
			if(j>=n)	y[j-n+1][i]=g[q[head]][i];
		}
	}
}
signed main(){
	a=read();b=read();n=read();
	for(int i=1;i<=a;i++){
		for(int j=1;j<=b;j++){
			s[i][j]=read();
		}
	}
	init1();
	init2();
	init3();
	init4();
	int ans=1e18;
	for(int i=1;i<=a-n+1;i++){
		for(int j=1;j<=b-n+1;j++){
			ans=min(ans,x[i][j]-y[i][j]);
		}
	}
	cout<<ans<<endl;
	return 0;
}

奶牛排队

设左端点为\(A\),右端点为\(B\)

那么此时显然存在一个性质,即从\(B\)向左第一个比\(B\)大的在\(A\)左侧,从\(A\)向右第一个比\(A\)小的点在\(B\)右侧。那么我们就可以想到单调栈。

每次新枚举到一个 \(B\) 时,先不将新位置入栈,此时的最大值栈顶就是第二个后缀最大值的位置。而维护后缀最小值的单调栈中的下标也是单调的,因此直接在最小值栈上二分即可找到最靠左的对应 \(A\) 的位置。更新答案即可。时间复杂度\(O(nlogn)\)

切蛋糕

最大不定长子段和问题。

记前缀和为\(S_i\),答案为:

\[\max \limits_{i=m}^n({\max \limits_{j=i-m+1}^i}{S_i-S_{j-1}}) \]

那么我们可以枚举\(i\),再用单调队列维护\(\min{S_j}\),时间复杂度\(O(n)\)

三步必杀

感觉日报讲得很清楚。
%

posted @ 2023-09-08 09:47  OIer_xxx2022  阅读(21)  评论(0)    收藏  举报