• 【枚举】AcWing 1236. 递增三元组


    1236. 递增三元组

    题目描述

    给定三个整数数组

    A = [ A 1 , A 2 , … A N ] , A=[A_1,A_2,…A_N], A=[A1,A2,AN],
    B = [ B 1 , B 2 , … B N ] , B=[B_1,B_2,…B_N], B=[B1,B2,BN],
    C = [ C 1 , C 2 , … C N ] , C=[C_1,C_2,…C_N], C=[C1,C2,CN],

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

    1. 1 ≤ i , j , k ≤ N 1≤i,j,k≤N 1i,j,kN
    2. A i < B j < C k A_iAi<Bj<Ck

    输入格式:

    第一行包含一个整数 N。

    第二行包含 N 个整数 A 1 , A 2 , … A N A_1,A_2,…A_N A1,A2,AN

    第三行包含 N 个整数 B 1 , B 2 , … B N B_1,B_2,…B_N B1,B2,BN

    第四行包含 N 个整数 C 1 , C 2 , … C N C_1,C_2,…C_N C1,C2,CN

    输出格式:

    一个整数表示答案。

    数据范围

    • 1 ≤ N ≤ 1 0 5 1≤N≤10^5 1N105
    • 0 ≤ A i , B i , C i ≤ 1 0 5 0≤A_i,B_i,C_i≤10^5 0Ai,Bi,Ci105

    输入样例

    3
    1 1 1
    2 2 2
    3 3 3
    
    • 1
    • 2
    • 3
    • 4

    输出样例

    27
    
    • 1

    方法一:暴力解法

    解题思路

    使用三层 for 循环,第一层 for 循环依次枚举 A 数组里的每个元素,第二层 for 循环依次枚举 B 数组里的每个元素,第三层 for 循环依次枚举 C 数组里的每个元素,然后判断当前三个元素是否满足条件 A[i] < B[j] && B[j] < C[k],满足情况的计数。

    但是如果数组最大长度为 1 0 5 10^5 105,遍历三个数组所需的时间 3 × 1 0 5 3 \times {10^5} 3×105,肯定会超时。

    代码

    #include 
    #include 
    using namespace std;
    
    const int N = 1e5 + 10;
    int A[N], B[N], C[N];
    int res = 0;
    int n;
    
    int main() {
        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];
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                for(int k = 0; k < n; k++)
                    if(A[i] < B[j] && B[j] < C[k])
                        res++;
        cout << res;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    复杂度分析:

    • 时间复杂度: O ( n 3 ) O(n^3) O(n3)
    • 空间复杂度:O(n)

    方法二:排序 + 二分

    解题思路

    先对 A 数组 和 C 数组进行排序。

    我们以 B 数组中的每个元素作为中间值,然后利用二分查找去 A 数组找到 < B[i] 的最大元素的位置,去 C 数组找到 > B[i] 的最小元素的位置,然后对应于每一个 B[i] 可构成的三元组个数为 l _ n u m × r _ n u m l\_num \times r\_num l_num×r_num,即 A 数组中 < B[i] 的元素的个数 * C 数组中 > B[i] 的元素的个数。

    代码

    #include 
    #include 
    using namespace std;
    
    const int N = 1e5 + 10;
    int A[N], B[N], C[N];
    int n;
    long long res = 0;
    
    int main() {
        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);
        sort(C, C + n);
        for(int i = 0; i < n; i++) {
            long long l_num = 0, r_num = 0;
            long long l = 0, r = n - 1, mid;
            while(l < r) {
                mid = l + (r - l + 1) / 2;
                if(A[mid] < B[i])    l = mid;
                else    r = mid - 1;
            }
            if(A[l] < B[i])
                l_num = l + 1;
            l = 0, r = n - 1;
            while(l < r) {
                mid = l + (r - l) / 2;
                if(C[mid] > B[i])    r = mid;
                else    l = mid + 1;
            }
            if(C[l] > B[i]) 
                r_num = n - l;
            res += l_num * r_num;
        }
        cout << res;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这里二分查找的算法可以用 STL 的内置函数来替代,代码更简洁一些。

    lower_bound(A, A+n, B[i]) 函数返回 A 数组中 >= B[i] 的第一个元素的位置。
    upper_bound(C, C+n, B[i]) 函数返回 C数组中 > B[i] 的第一个元素的位置。

    #include 
    #include 
    using namespace std;
    
    const int N = 1e5 + 10;
    int A[N], B[N], C[N];
    int n;
    long long res = 0;
    
    int main() {
        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);
        sort(C, C + n);
        for(int i = 0; i < n; i++) {
            long long l_num = 0, r_num = 0;
            l_num = lower_bound(A, A + n, B[i]) - A;
            r_num = n - (upper_bound(C, C + n, B[i]) - C);
            res += l_num * r_num;
        }
        cout << res;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    复杂度分析:

    • 时间复杂度:O(nlogn)
    • 空间复杂度:O(n)

    方法三:前缀和

    解题思路

    我们以 B 数组的每一个 B[i] 作为中间数,用 cnt_A 数组来统计 A 数组中的元素出现的次数,用 cnt_C 数组来统计 C 数组中的元素出现的次数。然后再使用 AS,CS 两个数组来存放 cnt_A 和 cnt_C 的前缀和。

    其中 AS[i] 和 CS[i] 表示 <= B[i] 的元素的个数,所以 A 数组中小于 B[i] 的元素个数为 AS[B[i] - 1],C 数组中大于 B[i] 的元素个数为 CS[N] - CS[B[i]],对于每一个 B[i] 求出 l_num 和 r_num,两者相乘,最后累加。

    代码

    #include 
    #include 
    
    using namespace std;
    
    const int N = 1e5 + 10;
    int A[N], B[N], C[N];
    int cnt_A[N], cnt_C[N];
    long long AS[N], CS[N];
    int n;
    long long res = 0;
    
    int main() {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) { scanf("%d", &A[i]); cnt_A[A[i]] += 1; }
        for(int i = 1; i <= n; i++) scanf("%d", &B[i]);
        for(int i = 1; i <= n; i++) { scanf("%d", &C[i]); cnt_C[C[i]] += 1; }
        
        AS[0] = cnt_A[0], CS[0] = cnt_C[0];
        for(int i = 1; i <= N; i++) AS[i] = AS[i - 1] + cnt_A[i];
        for(int i = 1; i <= N; i++) CS[i] = CS[i - 1] + cnt_C[i];
        
        for(int i = 1; i <= n; i++) {
            long long l_num = AS[B[i] - 1];
            long long r_num = CS[N] - CS[B[i]];
            res += l_num * r_num;
        }
        cout << res;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    复杂度分析:

    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

    方法四:双指针

    解题思路

    先对三个数组都进行排序。

    然后遍历 B 数组,以每个 B[i] 为中间数,使用双指针 l 和 r,用 l 来定位 A 数组中第一个大于等于 B[i] 的元素位置,此时的 l 即为 A 数组中小于 B[i] 的元素的个数 l_num;用 r 来定位 C 数组中第一个大于 B[i] 的元素位置,此时的 n - r 即为 C 数组中大于 B[i] 的元素的个数 r_num,最后对每个 B[i] 的 l_num 和 r_num 相乘最后累计求和。

    代码

    #include
    #include
    #include
    
    using namespace std;
    typedef long long LL;
    const int N = 1e5 + 10;
    
    int n;
    int a[N], b[N], c[N];
    
    int main()
    {
        cin>>n;
        for(int i = 0; i < n; i++) scanf("%d", &a[i]);
        for(int i = 0; i < n; i++) scanf("%d", &b[i]);
        for(int i = 0; i < n; i++) scanf("%d", &c[i]);
    
        sort(a, a + n);
        sort(b, b + n);
        sort(c, c + n);
    
        LL res = 0, l = 0, r = 0;
        for(int i=0;i<n;i++)
        {
            while(l < n && a[l] < b[i]) l++;
            while(r < n && c[r] <= b[i]) r++;
            res += l * (n - r);
        }
    
        cout << res << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    复杂度分析

    • 时间复杂度:O(nlogn)
    • 空间复杂度:O(n)
  • 相关阅读:
    SD-WAN — MPLS 广域网 VPN 技术解析
    Pandas数据类型转换
    【2011年数据结构真题】
    配置文件生成器-秒杀SSM的xml整合
    数据分析必备的能力
    简单上手_Kotlin,让开发更简洁
    postgresql之integerset
    c#与汇川plc通信
    1800亿参数,支持中文,3.5万亿训练数据!开源类ChatGPT模型
    Hive性能调优行之有效的优化方法
  • 原文地址:https://blog.csdn.net/lele_ne/article/details/126848859