递增三元组

给定三个整数数组

A=[A1,A2,…AN]

B=[B1,B2,…BN]

C=[C1,C2,…CN]

请你统计有多少个三元组 (i,j,k)满足:

  1. 1≤i,j,k≤N

  1. Ai<Bj<Ck

输入格式

第一行包含一个整数 N

第二行包含 N 个整数 A1,A2,…AN

第三行包含 N 个整数 B1,B2,…BN

第四行包含 N 个整数 C1,C2,…CN

输出格式

一个整数表示答案。

数据范围

1≤N≤105

0≤Ai,Bi,Ci≤10

输入样例:
3
1 1 1
2 2 2
3 3 3
输出样例:
27
题意:

请你统计有多少个三元组 (i,j,k)满足:

  1. 1≤i,j,k≤N

  1. Ai<Bj<Ck

分析:

常规暴力枚举A,B,C肯定是会超时的,我们可以尝试改变枚举次序,先枚举b,再查找出有多少个a小于b[i],有多少个c大于b[i]

接下来的优化都是针对“查找出有多少个a小于b[i],有多少个c大于b[i]”来进行的

方法一:

毋庸置疑,首先会想到二分查找

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int num[3][N];

int main(){
    int n;
    cin>>n;
    
    for(int i=0;i<3;++i){
        for(int j=0;j<n;++j){
            cin>>num[i][j];
        }
    }
    for(int i=0;i<3;++i){
        sort(num[i],num[i]+n);
    }
    ll ans=0;
    for(int i=0;i<n;++i){
//找出a数组最后一个小于b[i]的数,因为lower_bound表示找出第一个大于等于b[i]的数,那么-1就找出
//了最后一个小于b[i]的数
        int pos1=lower_bound(num[0],num[0]+n,num[1][i])-num[0]-1;
//upper_bound表示找出第一个大于b[i]的数
        int pos2=upper_bound(num[2],num[2]+n,num[1][i])-num[2];
        if(pos1>=0&&pos2<n){
//一定要转换为ll类型,不然会爆int
            ans+=(ll)(pos1+1)*(n-pos2);
        }
    }
    cout<<ans;
    return 0;
} 
方法二:

双指针

在查找的过程中,每次对于每一个b[i],我们都要搞一次二分查找,但其实没必要。

我们可以用一个指针一开始指向a数组的第一个元素,每次查询的时候继续更新它就行了,这样对于a,c数组而言,总共只用遍历数组一次

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int num[3][N];
int main(){
    int n;
    cin>>n;
    for(int i=0;i<3;++i){
        for(int j=0;j<n;++j){
            cin>>num[i][j];
        }
    }
    for(int i=0;i<3;++i){
        sort(num[i],num[i]+n);
    }
    ll ans=0;
//定义两个指针
    int a=0,c=0;
    for(int i=0;i<n;++i){
        
        while(a<n&&num[0][a]<num[1][i]){
            ++a;
        }
        while(c<n&&num[2][c]<=num[1][i]){
            ++c;
        }
        ans+=(ll)a*(n-c);
    }
    cout<<ans;
    return 0;
} 
方法三:

计数数组+前缀和

我们要找出“有多少个a小于b[i],有多少个c大于b[i]”,我们可以先用一个计数数组cnta来表示从1~1e5出现的次数,然后再算出他们的前缀和,表示小于等于这个数的有多少个

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int a[N],b[N],c[N],cnta[N],cntc[N];
int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;++i){
        cin>>a[i];
//将每个 a[i] 都加一,以方便后面求前缀和,cnta[a[i]]表示a[i]出现的次数,下同
        ++cnta[++a[i]];
    }
//求前缀和,省了个sum数组
    for(int i=1;i<N;++i){
        cnta[i]+=cnta[i-1];
    }
    for(int i=0;i<n;++i){
        cin>>b[i];
//b数组的每个数也都要加1,因为a,c数组都加1了
        ++b[i];
    }
    for(int i=0;i<n;++i){
        cin>>c[i];
        ++cntc[++c[i]];
    }
    for(int i=1;i<N;++i){
        cntc[i]+=cntc[i-1];
    }
    ll ans=0;
    for(int i=0;i<n;++i){
        ans+=(ll)cnta[b[i]-1]*(cntc[N-1]-cntc[b[i]]);
    }
    cout<<ans;
    return 0;
} 
总结:

有三重循环的情况下,我们可以考虑改变循环次序或者考虑将三层循环要优化成两层循环

对于查找数组中有多少个数小于一个给定的数,可以考虑二分,双指针,和计数数组保存每个数出现的次数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值