【枚举】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

输出样例

27

方法一:暴力解法

解题思路

使用三层 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;
}

复杂度分析:

  • 时间复杂度: 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;
}

这里二分查找的算法可以用 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;
}

复杂度分析:

  • 时间复杂度: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;
}

复杂度分析:

  • 时间复杂度: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;
}

复杂度分析

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

你可能感兴趣的:(#,枚举,算法之路,算法,c++,数据结构)