描述
给定三个整数数组
输入描述
第一行包含一个整数 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