2018.4.3 遇到一个很有意思的题目
题目:递增三元组
给定三个整数数组
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
请你统计有多少个三元组(i, j, k) 满足:
1. 1 <= i, j, k <= N
2. Ai < Bj < Ck
【输入格式】
第一行包含一个整数N。
第二行包含N个整数A1, A2, … AN。
第三行包含N个整数B1, B2, … BN。
第四行包含N个整数C1, C2, … CN。
对于30%的数据,1 <= N <= 100
对于60%的数据,1 <= N <= 1000
对于100%的数据,1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000
【输出格式】
一个整数表示答案
【样例输入】
3
1 1 1
2 2 2
3 3 3
【样例输出】
27
因为是 100000 的数据,如果暴力三重 for 循环只能过 30%
O( N ) 的做法(1)
可以利用前缀和,也就是类似于桶排序的标记法,每次出现一个元素,就在这个元素的桶上做标记,例如
2 4 7 8 9 2
那么形成的桶就是
1 2 3 4 5 6 7 8 9
0 2 0 1 0 0 1 1 1
很明显就是统计每一个出现的元素的次数
然后,问小于 5 的数有多少个 ?
不就是 2 个 2 , 1 个 4 吗,恰好 3 个数 < 5
问小于 6 的数有多少 ?
2 + 1 = 3 个
用前缀和的思想看这些桶
1 2 3 4 5 6 7 8 9
0 2 2 3 3 3 4 5 6
问,多少个数 < 5
就是 5-1 = 4 ,小于等于 4 的数有 3 个,也就是 小于 5 的数有 3 个
同理,后缀和
这道题目,可以先看 B 行,对B行每一个元素, pre[ B[i]-1 ] 这个地方就是A 行中有多少个比 B[i] 小的, Net[ B[i]+1 ] 就是C行中有多少个比 B[i] 大的。
很奇妙,复杂度 O( N )
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i <= int(n) ; ++i )
#define dew( i , j , n ) for ( int i = int(n-1) ; i >= int(j) ; --i )
const int N = 1e6 + 7 ;
int A[N] , B[N] , C[N] ;
int pre[N] , Net[N] ;
int main () {
int n ;
while ( ~scanf ( "%d" , &n ) ) {
memset ( pre , 0 , sizeof ( pre ) ) ;
memset ( Net , 0 , sizeof ( Net ) ) ;
rep ( i , 1 , n ) scanf ( "%d" , &A[i] ) , ++pre[ A[i] ] ;
rep ( i , 1 , n ) scanf ( "%d" , &B[i] ) ;
rep ( i , 1 , n ) scanf ( "%d" , &C[i] ) , ++Net[ C[i] ] ;
rep ( i , 1 , *max_element ( A + 1 , A + n + 1 ) )
pre[i] += pre[i-1] ;
dew ( i , 1 , *max_element ( C + 1 , C + n + 1 ) )
Net[i] += Net[i+1] ;
long long ans = 0 ;
rep ( i , 1 , n )
ans += pre[ B[i] - 1 ] * Net[ B[i] + 1 ] ;
cout << ans << endl ;
}
return 0 ;
}
时间复杂度 O (N) ,不过会受到数据大小的限制,因为,如果数据可以是 long long , 或者说 10000000000 这样的数字,是开不出 pre , Net 数组的。
O( N ) 的做法(2)
还有一种 O(N) 的解法。
和上面的类似。不过这里是直接利用桶排序,因为数据 < 100000, 用桶排序复杂度稳稳的 O(N)。
对 A , B , C 排序之后
同样是以 B 行为出发点,在 A 行设置一个指针(不一定是指针,某个递增量即可),在 C 行也设置一个指针,对 B 的每一个元素,比较 A , C 当前指针处的值,移动到适当位置,使得A指针以前的都比 B[i] 小,C指针以后的值都比 B[i] 大,既然分好区了,那就直接区间长度相乘。
因为最多就是访问完三个数组,复杂度 3N,也就是 O( N ) 。
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i <= int(n) ; ++i )
#define dew( i , j , n ) for ( int i = int(n-1) ; i >= int(j) ; --i )
const int N = 1e6 + 7 ;
const int M = 1e5 + 7 ;
int A[N] , B[N] , C[N] ;
int n , A_cnt , C_cnt ;
int book[M] ; ;
void Bucket_sort ( int *a , int n ) { // 桶排序, 数据不大的情况, 复杂度 O(N)
int top = 0 ;
int max_one = *max_element ( a + 1 , a + n + 1 ) ;
int min_one = *min_element ( a + 1 , a + n + 1 ) ;
memset ( book , 0 , sizeof ( book ) ) ;
rep ( i , 1 , n ) ++book[ a[i] ] ;
rep ( i , min_one , max_one )
rep ( j , 1 , book[i] ) // 看 i 这个数出现了多少次
a[++top] = i ; // 出现过的数字直接放进原数组
}
int main () {
while ( ~scanf ( "%d" , &n ) ) {
rep ( i , 1 , n ) scanf ( "%d" , &A[i] ) ;
rep ( i , 1 , n ) scanf ( "%d" , &B[i] ) ;
rep ( i , 1 , n ) scanf ( "%d" , &C[i] ) ;
Bucket_sort ( A , n ) ;
Bucket_sort ( B , n ) ;
Bucket_sort ( C , n ) ;
long long ans = 0 ;
A_cnt = C_cnt = 1 ;
rep ( i , 1 , n ) {
while ( A_cnt < n && A[A_cnt] < B[i] ) ++A_cnt ;
while ( C_cnt < n && C[C_cnt] <= B[i] ) ++C_cnt ;
int less = B[i] > A[n] ? n : A_cnt - 1 ;
int more = B[i] > C[n] ? 0 : n + 1 - C_cnt ;
ans += less * more ;
}
cout << ans << endl ;
}
return 0 ;
}
O ( N log N ) 解法
从 B 行开始,每一个元素,在 A 行二分搜索找到最后一个小于自己的数的位置
在 C行二分搜索第一个大于自己的数的位置
根据 Ai < Bj < Ck
复杂度 O( N log N )
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i <= int(n) ; ++i )
#define dew( i , j , n ) for ( int i = int(n-1) ; i >= int(j) ; --i )
const int N = 1e6 + 7 ;
int A[N] , B[N] , C[N] ;
int n ;
int Binary_less ( int *a , int len , int key ) {
int l = 1 , r = len ;
while ( l <= r ) {
int mid = ( l + r ) >> 1 ;
if ( key <= a[mid] )
r = mid - 1 ;
else
l = mid + 1 ;
}
return r ;
}
int Binary_more ( int *a , int len , int key ) {
int l = 1 , r = len ;
while ( l <= r ) {
int mid = ( l + r ) >> 1 ;
if ( key < a[mid] )
r = mid - 1 ;
else
l = mid + 1 ;
}
return l ;
}
int main () {
while ( ~scanf ( "%d" , &n ) ) {
rep ( i , 1 , n ) scanf ( "%d" , &A[i] ) ;
rep ( i , 1 , n ) scanf ( "%d" , &B[i] ) ;
rep ( i , 1 , n ) scanf ( "%d" , &C[i] ) ;
sort ( A+1 , A+n+1 ) ;
sort ( C+1 , C+n+1 ) ;
long long ans = 0 ;
rep ( i , 1 , n ) {
int less = Binary_less ( A , n , B[i] ) ;
int more = n + 1 - Binary_more ( C , n , B[i] ) ;
ans += less * more ;
}
cout << ans << endl ;
}
return 0 ;
}
和三重循环相比,O ( N ) 和 O( N logN ) 的算法,很重要的一点就是,从第二行出发,然后在第一行找更小的,在第三行找更大的,这样降了一级的复杂度。