数学基础和线性结构(快速幂、双指针、单调栈、差分和前缀和)

数学基础和线性结构

①快速幂

给定三个正整数a、b、m,求a^b%m

法一:时间复杂度为O(b),会爆long long

typedef long long LL;
LL LLpow(LL a, LL b, LL m)
{
	LL ans = 1;
	for (int i = 0; i < b; i++)
		ans = ans * a%m;
	return ans;
}

法二:快速幂,时间复杂度为O(logb),不会爆

快速幂核心:

①如果b是奇数,那么有ab=a*a(b-1)

②如果b是偶数,那么有ab=[a(b/2)]*[a^(b/2)]

typedef long long LL;
LL binaryPow(LL a, LL b, LL m)
{
	if (b == 0)
		return 1;
	if (b % 2 == 1)
		return a * binaryPow(a, b - 1, m) % m;
	else
	{
		LL mul = binaryPow(a, b / 2, m);
		return mul % mul%m;
	}
}

注意:

①如果初始时a>=m,先让a对m取模再进入函数。

②如果m=1,在函数外特判为0(任何数对1取模一定等于0)。

②two pointers(双指针/尺取法)

什么情况下能使用尺取法?

①所求解答案为一个连续区间 。

②对于一个左端点 L, [L,n] 的区间是否满足条件是单调的。

例一:给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数是a和b,使得它们的和恰好为M,输出所有满足条件的方案。

法一:时间复杂度为O(n^2)

for (int i = 0; i < n; i++)
	for (int j = i + 1; j < n; j++)
		if (a[i] + a[j] == M)
			cout << a[i] << " " << a[j] << endl;

法二:two pointers,时间复杂度为O(n)

分别令指针i和j指向序列的第一个和最后一个元素

①如果满足a[i]+a[j]==M,令i++,j–。

②如果满足a[i]+a[j]>M, 令j–。

③如果满足a[i]+a[j]<M,令i++。

int i = 0, j = n - 1;
while (i < j)
{
	if (a[i] + a[j] == M)
	{
		cout << a[i] << " " << a[jj] << endl;
		i++;
		j--;
	}
	else if (a[i] + a[j] < M)
		i++;
	else
		j--;
}

例二:将两个递增序列A,B合并为一个递增序列C。

设定两个下标i和j,初值为0。

①若A[i]<=B[j],A[i]加入序列C,i++。

②若A[i]>B[j],B[j]加入序列C,j++。

上面的分支操作直到i和j中的一个到达序列末端为止,然后将另一个序列的所有元素依次加入到序列C中。

int merge(int A[], int B[], int C[], int n, int m)
{
	int i = 0, j = 0, index = 0;
	while (i < n&&j < m)
		if (A[i] <= B[j])
			C[index++] = A[i++];
		else
			C[index++] = B[j++];
	while (i < n)
		C[index++] = A[i++];
	while (j < m)
		C[index++] = B[j++];
	return index;
}

例三:长度为 n 的数组,每个数均为正整数 。给出正整数 S,要求 O(n) 内求出长度最小的连续区间, 使 S ≤ 区间和。以 [5 1 3 5 10 7 4 9 2 8],S = 15为例。

维护双指针 L、R,初始 L = R = 1,sum = a[L] 。

①当 sum >= S 时, 符合要求,用 (R-L+1) 更新答案,且 L++;若 L = R,则 L++, R++ 。

②当 sum < S 时,不符合要求,R++。

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int A[10] = { 5,1,3,5,10,7,4,9,2,8 };
	int n = 10, S = 15;
	int L = 0, R = 0, sum = A[L];
	int ans = 100000;
	while (R != n)
	{
		if (sum >= S)
		{
			ans = min(ans, R - L + 1);
			if (L == R)
				sum += A[R++];
			sum -= A[L++];
		}
		else
			sum += A[++R];
	}
	cout << ans << endl;
}

例四:一个长度为 n 的字符串 s,其中仅包含 ‘A’, ‘B’, ‘C’, ‘D’ 四种字符(n 为 4 的倍数。如果四种字符在字符串中出现次数均为 n/4,则其为一 个平衡字符串 。现可以将 s 中连续的一段替换成任意字符,使其变为一 个平衡字符串,问替换子串的最小长度?

①用sumq, sumw, sume, sumr来记录不包含区间[ L, R ] 时,字符’Q’, ‘W’,‘E’,'R’的个数 。
②先通过替换,使得4类字符的数量一致,然后判断剩余的空闲位置是否是4的倍数。

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
string str;
int sum[100] = { 0 };//Q 81;W 87;E 69;R 82
int L = 0, R = 0, ans = 999999;
bool check()
{
	int maxx = max(max(sum[81],sum[87]),max(sum[69], sum[82]));
	int free = R - L + 1 - ((maxx - sum[81]) + (maxx - sum[87]) + (maxx - sum[69]) + (maxx - sum[82]));
	if (free >= 0 && free % 4 == 0)
		return true;
	else
		return false;
}
int main()
{
	cin >> str;
	for (int i = 0; i < str.size(); i++)
		sum[str[i]]++;

	if (sum[81] == str.size() / 4 && sum[87] == str.size() / 4 && sum[69] == str.size() / 4 && sum[82] == str.size() / 4)
	{
		cout << 0 << endl;
		return 0;
	}

	sum[str[0]]--;
	while (R < str.size())
	{
		if (check())
		{
			ans = min(ans, R - L + 1);
			if (L == R)
				sum[str[++R]]++;
			sum[str[L++]]--;
		}
		else
			sum[str[++R]]--;
	}
	cout << ans << endl;
}

③单调栈

如单调递增栈:栈内递增

当前元素为x,若栈顶元素<x,x入栈,否则不断弹出栈顶元素。

当元素x被放入单调递增栈时,其栈内左边的数是原始序列中第一个小于等于它的数。

若栈顶元素被弹出,则当前元素为右边第一个比它小的数。

如序列{3,1,4,5,2,7}

每次入栈后栈内序列为:

{3}

{1}

{1,4}

{1,4,5}

{1,2}

{1,2,7}

例题最大矩形面积

青石板路的尽头堆满了财宝。小 L 感到很一阵阵失望,只能先搬走一部分财宝了。

财宝是一个个矩形紧紧挨在一起,第 iii 个矩形宽度为 111 ,高度是 hih_ihi

小 L 是一个 不会贪心 不贪心的人,所以决定只拿走最大矩形的面积这么多。

拿着拿着,小 L 突然想到,其实这个财宝墙后面还是有路的。

在这里插入图片描述

input

7
2 1 4 5 1 3 3 

output

8

:利用单调栈求出每一个长方块左边和右边第一个比它小的长方块,长方块编号1到7

求解后:

1 0 2
2 0 8
3 2 5
4 3 5
5 0 8
6 5 8
7 5 8

最终长方形的面积为h[i]*(L[i]-R[i])。

代码

写法一(手写栈)

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int h[N], q[N], L[N], R[N];
typedef long long LL;
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> h[i];
	h[0] = h[n + 1] = -1;

	int top = 0;
	for (int i = 1; i <= n; i++)
	{
		while (top != 0 && h[q[top]] >= h[i])
			top--;
		L[i] = (top == 0 ? 0 : q[top]);
		q[++top] = i;
	}

	top = 0;
	for (int i = n; i >= 1; i--)
	{
		while (top != 0 && h[q[top]] >= h[i])
			top--;
		R[i] = (top == 0 ? n + 1 : q[top]);
		q[++top] = i;
	}

	LL S = 0;
	for (int i = 1; i <= n; i++)
		S = max(S, (LL)h[i] * (R[i] - L[i] - 1));

	cout << S << endl;
}

写法二(STL栈)

#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 100010;
int h[N], q[N], L[N], R[N];
typedef long long LL;
int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> h[i];

	stack<int> s;
	for (int i = 1; i <= n; i++)
	{
		while (!s.empty() && h[s.top()] >= h[i])
			s.pop();
		L[i] = (s.empty() ? 0 : s.top());
		s.push(i);
	}

	stack<int> q;
	for (int i = n; i >= 1; i--)
	{
		while (!q.empty() && h[q.top()] >= h[i])
			q.pop();
		R[i] = (q.empty() ? n + 1 : q.top());
		q.push(i);
	}

	LL S = 0;
	for (int i = 1; i <= n; i++)
		S = max(S, (LL)h[i] * (R[i] - L[i] - 1));

	cout << S << endl;
}

④单调队列

单调队列的核心功能为「求出数组中每一个元素其固定区间范围内的最大 / 小值」。

并且由于每个元素出队、入队最多一次,因此总的时间复杂度为 O(n)

如单调递增队列

限制滑动窗口大小为3

若对内元素个数>=3,则队首元素不断出队,直至队内元素个数<3。

当前元素为x,若队尾元素<x,则x入队,否则队尾元素不断出队。

如序列{1,3,-1,-3,5,3,6,7}

1 3 -1 -3 5 3 6 7

​ ③ ④ ⑤ ⑥ ⑦ ⑧

③到⑧号元素入队后,队列情况如下:

{-1} 说明1到3的最小值是-1

{-3} 说明2到4的最小值是-3

{-3,5} 说明3到5的最小值是-3

{-3,3} 说明4到6的最小值是-3

{3,6} 说明5到7的最小值是3

{3,6,7} 说明6到8的最小值是3

例题:**滑动窗口求最小值和最大值

有一行 nnn 个数。输出每 kkk 个相邻数字的最大值和最小值。

input

8 3
1 3 -1 -3 5 3 6 7

output

-1 -3 -3 -3 3 3
3 3 5 5 6 7

在这里插入图片描述

代码

#include<iostream>
#include<cstdio>
#include<deque>
using namespace std;
int n, k, a[1000010];
deque<int> q;
int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
		cin >> a[i];
	for (int i = 1; i <= n; ++i)
	{
		if (!q.empty() && (i - q.front()) >= k)
			q.pop_front();
		while (!q.empty() && a[i] <= a[q.back()])
			q.pop_back();
		q.push_back(i);
		if (i >= k)
			cout << a[q.front()] << " ";
	}

	cout << endl;
	q.clear();
	for (int i = 1; i <= n; ++i)
	{
		if (!q.empty() && (i - q.front()) >= k)
			q.pop_front();
		while (!q.empty() && a[i] >= a[q.back()])
			q.pop_back();
		q.push_back(i);
		if (i >= k)
			cout << a[q.front()] << " ";
	}
	cout << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值