平日小算法笔记 (1) 前缀和求递增三元组

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

【输出格式】 
一个整数表示答案

【样例输入】 

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 ) 的算法,很重要的一点就是,从第二行出发,然后在第一行找更小的,在第三行找更大的,这样降了一级的复杂度。




猜你喜欢

转载自blog.csdn.net/nishisiyuetian/article/details/79808362
今日推荐