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

 


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值