递增三元组

题目描述

给定三个整数数组

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. 1 \leq i, j, k \leq N1≤i,j,k≤N;

  2. 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相当于两个旗杆,一直缩小.
//最后合在一起的时候也就锁定了想要的值

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值