递增三元组 (二分,双指针,前缀和)

描述
给定三个整数数组
在这里插入图片描述
输入描述
第一行包含一个整数 N。
第二行包含 N个整数 A1, A2, ⋯AN。
第三行包含 N 个整数 B1, B2,⋯BN。
第四行包含 N个整数 C_1, C_2,…C*N。
其中,1≤N≤1e5,0≤Ai,Bi,Ci≤1e5。
输出描述
输出一个整数表示答案。
用例输入 1

3
1 1 1
2 2 2
3 3 3

用例输出 1

27

思路

for循环枚举第一行的数,然后找到第二行中第一个比第一行大的数,二分查找第三行的第一个比第二行大的数;

AC代码1

#include<iostream>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxn = 1e5+5;
int a[maxn],b[maxn],c[maxn];
int n;
void solve(){
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + n);
    sort(c + 1, c + 1 + n);
    ll ans = 0;
    ll j = 1;
    for(int i = 1 ; i <= n ; i++){
        while(j <= n && a[j] < b[i]) j++; 
        int t2 = upper_bound(c + 1, c + 1 + n , b[i]) - c;
        // 在c集合中找第一个大于b[i]的位置
        ans += (j - 1) * (n - t2 + 1); 
    }
    cout << ans << endl;
}
int main(){
    cin >> n;
    for(int i = 1 ; i <= n ; i++) cin >> a[i];
    for(int i = 1 ; i <= n ; i++) cin >> b[i];
    for(int i = 1 ; i <= n ; i++) cin >> c[i];
    solve();
    return 0;
}

AC代码2

题目分析
首先考虑暴力做法,三个数组嵌套枚举,O(n3)的时间复杂度,n≤105一定会超时。

尝试通过枚举的次序进行优化本题,先枚举B数组,在A中寻找小于B[i]的数的个数cnt1,在C中寻找大于B[i]的数的个数cnt2,带有B[i]的合法选择数就是cnt1*cnt2。

用暴力查找时间总的时间复杂度为O(n2),还是会超时。

二分
既然是查找,那么可以考虑进行二分查找,查找前先通过排序预处理三个数组,排序时间复杂度O(nlog2n),枚举B的所有元素+查找A,C中的元素时间复杂度也是O(nlog2n),总的时间复杂度降为O(nlog2n)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 1e5+10;
int num[3][N];

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 0; i < 3; ++i) 
        for(int j = 1; j <= n; ++j) 
            scanf("%d", &num[i][j]);
    for(int i = 0; i < 3; ++i)
        sort(num[i]+1, num[i]+n+1);

    LL ans = 0;
    //枚举B,寻找A满足的个数以及C满足的个数相乘
    for(int i = 1; i <= n; ++i) {
        int key = num[1][i];
        //A中二分查找第一个小于key的数的下标
        int pos1 = lower_bound(num[0]+1, num[0]+n+1, key)-num[0]-1;
        //C中二分查找第一个大于key的数的下标
        int pos2 = upper_bound(num[2]+1, num[2]+n+1, key)-num[2];
        if(pos1 >= 1 && pos2 <= n) {
            ans += (LL)pos1*(n-pos2+1);
        }
    }
    cout<<ans<<endl;
    return 0;
}

AC代码3

双指针
进一步对查找进行优化,对于排过序的数组A和B,寻找A中小于B[i]的元素的个数可以考虑双指针算法,因为每个指针最多移动n次,故查找的时间复杂度降到O(n),查找C与查找A同理,只是找第一个大于B的位置。
只需要将上述二分程序中的

//二分
for(int i = 1; i <= n; ++i) {
    int key = num[1][i];
    //A中二分查找第一个小于key的数的下标
    int pos1 = lower_bound(num[0]+1, num[0]+n+1, key)-num[0]-1;
    //C中二分查找第一个大于key的数的下标
    int pos2 = upper_bound(num[2]+1, num[2]+n+1, key)-num[2];
    if(pos1 >= 1 && pos2 <= n) {
        ans += (LL)pos1*(n-pos2+1);
    }
}
更改为

//双指针
int a = 1, c = 1;
for(int i = 1; i <= n; ++i) {
    int key = num[1][i];
    while(a<=n && num[0][a] < key) a++;
    while(c<=n && num[2][c] <= key) c++;

    ans += (LL)(a-1)*(n-c+1);
}
完整的双指针程序为:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 1e5+10;
int num[3][N];

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 0; i < 3; ++i) 
        for(int j = 1; j <= n; ++j) 
            scanf("%d", &num[i][j]);
    for(int i = 0; i < 3; ++i)
        sort(num[i]+1, num[i]+n+1);

    LL ans = 0;
    //枚举B,寻找A满足的个数以及C满足的个数相乘
    int a = 1, c = 1;
    for(int i = 1; i <= n; ++i) {
        int key = num[1][i];
        while(a<=n && num[0][a] < key) a++;
        while(c<=n && num[2][c] <= key) c++;

        ans += (LL)(a-1)*(n-c+1);

    }
    cout<<ans<<endl;
    return 0;
}

AC代码4

前缀和
之前的双指针算法时间复杂度的瓶颈为:排序O(nlog2n)
考虑是否可以不排序在O(n)的时间内解决此问题呢?

既然要排序实现快速的查找A中小于B[i]的数的个数,可以将数组A中所有元素出现的次数存入一个哈希表中,因为数组中元素的范围只有n5n5, 可以开一个大的数组cnta 作为哈希表。

在枚举B中元素时,我们需要快速查找找小于B[i]的所有元素的总数,只需要在枚举之前先将求出表中各数的前缀和即可。

查找C与查找A同理可得。

C++代码实现

//前缀和
#include <iostream>
#include <cstdio>

using namespace std;

typedef long long LL;
const int N = 1e5+10;
int A[N], B[N], C[N];
int cnta[N], cntc[N], sa[N], sc[N];

int main() {
    int n;
    scanf("%d", &n);
    //获取数i在A中有cntc[i]个,并对cnt求前缀和sa
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &A[i]);
        cnta[++A[i]]++;
    }
    sa[0] = cnta[0];
    for(int i = 1; i < N; ++i) sa[i] = sa[i-1]+cnta[i];
    //B只读取即可
    for(int i = 1; i <= n; ++i) scanf("%d", &B[i]), B[i]++;

    //获取数i在C中有cntc[i]个,并对cnt求前缀和sc
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &C[i]);
        cntc[++C[i]]++;
    }
    sc[0] = cntc[0];
    for(int i = 1; i < N; ++i) sc[i] = sc[i-1]+cntc[i]; 

    //遍历B求解
    LL ans = 0;
    for(int i = 1; i <= n; ++i) {
        int b = B[i];
        ans += (LL)sa[b-1] * (sc[N-1] - sc[b]);
    }
    cout<<ans<<endl;
    return 0;
}

运行时间
二分:725 ms
双指针:454 ms
前缀和:179 ms

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值