题目描述
给定三个整数数组
A = [A_1, A_2, \cdots A_N]A=[A1,A2,⋯AN],
B = [B_1, B_2, \cdots B_N]B=[B1,B2,⋯BN],
C = [C_1, C_2, \cdots C_N]C=[C1,C2,⋯CN],
请你统计有多少个三元组 (i, j, k)(i,j,k) 满足:
-
1 \leq i, j, k \leq N1≤i,j,k≤N;
-
A_i < B_j < C_kAi<Bj<Ck。
输入描述
第一行包含一个整数 NN。
第二行包含 NN 个整数 A_1, A_2, \cdots A_NA1,A2,⋯AN。
第三行包含 NN 个整数 B_1, B_2, \cdots B_NB1,B2,⋯BN。
第四行包含 NN 个整数 C_1, C_2, \cdots C_NC1,C2,⋯CN。
其中,1 \leq N \leq 10^5, 0 \leq Ai, Bi, Ci \leq 10^51≤N≤105,0≤Ai,Bi,Ci≤105。
输出描述
输出一个整数表示答案。
输入输出样例
示例
输入
3
1 1 1
2 2 2
3 3 3
输出
27
运行限制
- 最大运行时间:2s
- 最大运行内存: 256M
一开始的解题过程
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a[100000],b[100000],c[100000];
int n;
long long ans=0;
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
for(int i=0;i<n;i++)
cin>>b[i];
for(int i=0;i<n;i++)
cin>>c[i];
sort(a,a+n);//直接写出首尾就ok了
sort(c,c+n);
int left,right;
for(int i=0;i<n;i++)//枚举b
{
//找出比b小的最大的a,比b大的最小的c
left=0,right=n-1;
while(left<right)
{
int mid=(left+right+1)/2;
if(b[i]<=a[mid])//当前a的值大于等于b,我也不要了
//a太大,我就缩小
//注意,这个小于等于要放在一起因为都归为mid不可取的一边
//如果只写<,那么等于在下面就无法把等于这种情况去除.
right=mid-1;
else
left=mid;//当前mid满足条件可取
}
int x=right;
left=0,right=n-1;
while(left<right)
{
int mid=(left+right)/2;
if(b[i]>=c[mid])//条件不满足题意,mid值不要
left=mid+1;
else
right=mid;//符合,mid可取
}
int y=right;
if(a[x]<b[i]&&b[i]<c[y])
{
// ans+=(x+1)*(n-y);err
ans+=(long long)(x+1)*(n-y);
//两个相乘是因为排列组合
//定下1个b,每一种a都有n-y个c的情况.
//一个数据错了,这里相乘计算大数据会溢出
//所以要扩大数据类型!
//但是给x,y修改类型竟然也能过,我不知道为什么
//但逻辑来讲还是改这里的数据类型比较严谨
}
}
cout<<ans;
return 0;
}
详解
#include<bits/stdc++.h>
//核心为二分查找.
//线性:从小到大,或者从大到小才用二分
//所以说先排序后二分,这样才有线性
//二分法不是死的,它只是一个方法,
//具体要看你要求的数值是什么,自己要带入情景(最好自己画图)
//然后确定如何移动left和right
//带入理解然后使用,left和right的值都是要自己经过思考得出的
//加一减一还是直接等于middle需要结合实际判断
using namespace std;
int main() {
int n;
cin >> n;
int a[n];
int b[n];
int c[n];
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
cin >> b[i];
}
for (int i = 0; i < n; i++) {
cin >> c[i];
}
sort(a, a + n);
sort(c, c + n);
//排序a,与c,为了满足线性的二分
//b不用排,通过枚举b确定所有a,c的可能
long ans = 0;
for (int i = 0; i < n; i++) {//枚举b
int left = 0;
int right = n - 1;
while (left < right) {//找到a中比b小的最大值
int middle = (left + right + 1) >> 1;
//加一是因为中间值直接给了左边界
//因为二分是left小于right作为循环条件
//如left = 3,right = 4
//这样left就会一直等于3死循环
//一般设置都是left<right
//因此为了避免死循环,要么left就一定要移动如left=middle+1
//循环终止是left==right,所以如果left不动那么middle就移动
//加一后middle = 4,left就等于4,循环终止
//注意前提是left<right,如果反过来就是看right了
if (a[middle] < b[i]) left = middle;//check
//小于的值满足条件,保留中间值
else right = middle - 1;
//a不能大于等于b,所以要舍弃中间值
}
int x = right;
left = 0;
right = n - 1;
while (left < right) //b和c
{
int middle = (left + right) >> 1;
//这里没加一是因为left移动了,right动不动无所谓
if (c[middle] > b[i])
right = middle;//保留
else left = middle + 1;
//舍弃中间值
}
int y = right;
//求得x,y后,我们就能根据这两个边界来求出所有满足条件的
//a与b,具体如下
//对着一个b而言有多少个比他小的,有多少个比他大的
if (a[x] < b[i] && b[i] < c[y])
//加这个条件再次判断是防止输入的数据根本就不合法
//所以加入条件,只有条件合法,我才增加ans值
ans = ans + (long)(x + 1) * (n - y);
//x+1是因为下标从0开始,要加a[0]
//n-y实质上是(n-1)-y+1,因为y自己本身也是一个值
//末尾的下标是n-1不是n,相减会丢失y本身这个值
//对这种疑惑,你直接随便写几个数看看结果对不对就行了
}
cout << ans << endl;
return 0;
}
//理解误区:
//这里的二分是为了查找,而不是返回那个mid值!mid只是起到分割的作用
//条件的终点就是left==right,不是left>right,所以无论返回left还是right都是对的
//left和right就是你要返回的值。left和right相当于两个旗杆,一直缩小.
//最后合在一起的时候也就锁定了想要的值