🌹作者:云小逸
📝个人主页:云小逸的主页
📝Github:云小逸的Github
🤟motto:要敢于一个人默默的面对自己,==强大自己才是核心==。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天来之前,我们一起面朝大海,春暖花开!==🤟 👏专栏:C++👏 👏专栏:Java语言👏👏专栏:Linux学习👏
👏专栏:C语言初阶👏👏专栏:数据结构👏👏专栏:备战蓝桥杯👏
@TOC
前言
今天我们继续学习算法,加油。这篇文章写的是三元组问题。希望这篇可以有幸帮助到你,码字不易,请多多支持。
题目描述
给定一个长度为 的序列,求其中有多少个三元组 满足 且 。
输入格式
第一行包含整数 。
第二行包含 个整数,表示完整序列。
输出格式
输出一个整数,表示满足条件的三元组个数。
数据范围
,
输入样例:
5
3 1 2 4 5
输出样例:
2
题解
算法1
(暴力枚举)
题目很容易想到使用三重循环暴力枚举所有的三元组 , 但这样的时间复杂度是 的,无法通过本题。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100003;
int a[maxn], n, ans;
signed main()
{
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i];
for(int i=1; i<=n; i++)
for(int j=i+1; j<=n; j++)
for(int k=j+1; k<=n; k++)
if(a[i]<a[k] && a[k]<a[j]) ans++;
cout << ans << endl;
return 0;
}
算法2
(求出两边增长序列)
下面为正解。
我们可以把一个合法的三元组拆分成两个增长的区间,并且中间点是固定的。如下图所示:

即先枚举中间数,再分别枚举左边最小数和右边最大数。 如果已经枚举到了,那么在这些位置中必定存在一个最小的,在这些位置中也必定存在一个最大的,这样就可以将所有符合条件的三元组划分为个区间:对于一个特定的,其对答案的贡献就是前面的数和后面的数的数量乘积。
而求出这些前缀最小值和后缀最大值是可以通过单调栈实现的。具体方法是:
- 对于每个位置,维护一个单调递增的栈,栈中一开始存放的是从左到右第一个大于等于的数
- 栈里面的数是递增的,每次加入一个新数时,将栈中小于的数全部弹出
- 此时栈顶就是前面最小的数
同理,我们也可以维护右边的后缀最大值,这样就能快速求出在左边比小的数和在右边比大的数。时间复杂度降为 。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100003;
int a[maxn], n, ans;
int mx[maxn], st[maxn], top;
signed main()
{
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i];
top = 0;
for(int i=1; i<=n; i++)
{
while(top && a[st[top]]>=a[i]) top--;
if(top) mx[i] = st[top];
st[++top] = i;
}
top = 0;
for(int i=n; i>=1; i--)
{
while(top && a[st[top]]<=a[i]) top--;
if(top) ans += mx[i]? (top-st[mx[i]]) : top;
st[++top] = i;
}
cout << ans << endl;
return 0;
}
算法3
(离散化+二分查找)
另外一种解决方法是先对序列进行离散化,然后按照升序排列。对于这样的三元组,我们可以枚举和,然后在这一段中寻找最小的满足条件,即,如果找到这样一个数,则该数前面的所有数都是符合条件的。
考虑如何寻找,显然朴素的方法是从开始往前扫描,找到第一个小于的数,再判断是否有。这样的时间复杂度是 的,无法通过本题。
不过我们可以使用二分查找来优化这个过程: 对于每个和,我们维护两个大小为 的数组和,分别表示将序列按照升序排列得到的新序列和降序排列得到的新序列。对于一个固定的位置,我们只需要找到中,在前面且比小的元素中的,以及在后面且比小的元素中的即可更新答案。
具体实现详见代码。
C++ 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100003;
int n, ans;
int a[maxn], sorted[maxn], reversed[maxn];
int s[maxn], r[maxn];
signed main()
{
cin >> n;
for(int i=1; i<=n; i++)
{
cin >> a[i];
sorted[i] = reversed[i] = a[i];
}
sort(sorted+1, sorted+n+1);
sort(reversed+1, reversed+n+1, greater<int>());
for(int i=1; i<=n; i++)
{
int j = lower_bound(sorted+1, sorted+n+1, a[i]) - sorted;
ans += (j-1) - (lower_bound(reversed+1, reversed+n+1, a[i], greater<int>()) - reversed);
int k = lower_bound(s+1,s+i+1,a[i])-s-1;
if(k) ans -= (lower_bound(r+1,r+k+1,a[i],greater<int>())-r);
s[k+1] = a[i]; r[k+1] = min(r[k],a[i]);
}
cout << ans << endl;
return 0;
}
最后
十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:
1.把时间尺度拉长,拉长十年看当下
2.不说负面情绪,只描述事实;
3.越专注于过好自己,能量和幸运越会照顾你;
只解决问题,不做没有意义的担心,输了就认;
4.学会原谅自己,要允许自己做错事,允许自己出现情绪波动,我知道你已经很努力很努力在做好了
5.所有你害怕的、想逃避的事情,最终都要面对,既然这样不如选择坦然面对。即使结果不如人愿,没关系,至少这个过程是享受的,而不是一路带着恐惧和害怕。
最后如果觉得我写的还不错,请不要忘记==点赞==✌,==收藏==✌,加==关注==✌哦(。・ω・。)
愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚==菜鸟==逐渐成为==大佬==。加油,为自己点赞!