1. 程式人生 > >平日小演算法筆記 (1) 字首和求遞增三元組

平日小演算法筆記 (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 字首遞增三元

2018.4.3 遇到一個很有意思的題目 題目:遞增三元組 給定三個整數陣列  A = [A1, A2, … AN],  B = [B1, B2, … BN],  C = [C1, C2, … CN],  請你統計有多少個三元組(i, j, k) 滿足:  1. 1 &l

關於演算法筆記1中氣泡排序的bug

問題的發現 近一段正在配置openGL的環境,想使用VScode作為開發環境。剛剛配置好了VScode的C++環境,使用的編譯器是Clang++,測試使用的程式碼是之前的寫的BubbleSort.cpp。很奇怪,輸出的結果只有一位數字2,在之前都是可以跑通的。問

Lua學習筆記1: HelloWorld資料型別

Lua是一個輕量級的指令碼語言,由c語言編寫,容易嵌入到應用中,深受遊戲開發者的青睞 環境安裝 選用SciTE作為lua的IDE 可以在github找到這個開源的軟體 SciTE下載連結 安裝好之後開啟 環境搭建完成 HelloWorld Fil

C語言筆記1

列舉型別的大小是4,和一個int整形大小一樣   就是最後一個逗號後面的表示式的值,比如: int a=1,b; b=(a+1,a+2,a+3); 那麼b的值就是a+3,也就是4   函式名   :printf  函式原型:in

KCF跟蹤演算法學習筆記1

KCF跟蹤是相關濾波跟蹤器最具有代表性的,但是作為一個從來沒接觸過跟蹤演算法,線代苦手來說,看懂KCF中的原理簡直是難上加難,網上所有的相關文件要說也夠多了,可惜水平太差,看完以後只想問兩個問題,這是什麼?這又是什麼?再難啃的骨頭也是要啃的,所以決定把目前還一知半解的學習內容

《機器學習實戰》學習筆記1——k-近鄰演算法

1 k-近鄰演算法概述 k-近鄰演算法,採用測量不同特徵值之間的距離方法進行分類。 工作原理: 存在一個樣本資料集,也成為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中的每一資料與所屬分類的對應關係。輸入沒有標籤的新資料後,將新資

常用演算法之:1、最二乘法1

深度學習發展到如今的地位,離不開下面這 6 段程式碼。本文介紹了這些程式碼的創作者及其完成這些突破性成就的故事背景。每個故事都有簡單的程式碼示例,讀者們可以在 FloydHub 和 GitHub 找到相關程式碼。 最小二乘法 所有的深度學習演算法都始於下面這個數學公式(

matlab學習筆記1——粒子群優化演算法PSO的程式實現

     本文內容參考matlab R2016a完全自學一本通。     粒子群優化演算法(PSO)屬於進化演算法的一種,它從隨機解出發,通過迭代找到最優解。該演算法通過適應度來評價解的品質,並通過追隨當前搜尋到的最優值來尋找全域性最優。     假設在一個D維的目標搜尋空間

微信程式學習筆記1

首先,註冊帳號 這裡指的是微信公眾平臺的註冊帳號,不是微信App賬號。 訪問https://mp.weixin.qq.com/,如果沒有帳號,輸入郵箱,註冊一個。註冊過程中會讓你從四個類別(微信訂閱號、服務號、企業微信、小程式)中選擇一個,選擇“小程式"就好了。 後續輸

Python程式碼筆記1輾轉相除法/歐幾里得演算法最大公約數gcdm,n

歐幾里得演算法求最大公約數:輾轉相除法 具體做法:用較小數除較大數,再用出現的餘數(第一餘數)去除除數,再用出現的餘數(第二餘數)去除除數,如此反覆,直到最後餘數是0為止。如果是求兩個數的最大公約數,

堆的構建C++實現--演算法拾遺1

現在開始想把平時遇到的零星的演算法都記錄在這個板塊裡,因為若是沒有平常的記錄而只是將曾經實現過的程式碼爛在資料夾裡的話確實不是一個很好的做法啊。畢竟這是筆記而非展示給那個平臺下的報告,所以有的時候寫的隨便一點。有時演算法具體實現上的參考出處我也會做上註釋。

程式《舊島》專案筆記1-專案開發準備

專案開發準備 1、開發工具 對於小程式專案,我們除了使用小程式官方開發工具外,還可以使用第三方開發工具,比如vscode、vim、sublime、webstorm等等。vscode中,提供了一些好用的外掛,便於開發。 2、樣式繼承 小程式中,很多樣式都是不可以繼承的,

微信:程式學習筆記1

微信的小程式已經推出了,相繼的教程也有。官方網頁推出API和介面說明,目前來講暫時足夠。 可以進入該連結檢視。微信官網 今天嘗試一下下載該web的開發工具,從目前來講,使用了一個下午。目前版本給人的感覺還有待繼續提升的恐懼。從編碼的體驗來講,對一個重度使用者

強機器人學習筆記1

問題1:roscore到底是什麼,什麼情況下必須先執行roscore ERROR: Unable to communicate with master! 這是不執行roscore就rosnode list的下場。 但是小強機器人的遠端連結 ssh [email p

機器學習筆記1 感知機演算法 之 實戰篇

我們在上篇筆記中介紹了感知機的理論知識,討論了感知機的由來、工作原理、求解策略、收斂性。這篇筆記中,我們親自動手寫程式碼,使用感知機演算法解決實際問題。 先從一個最簡單的問題開始,用感知機演算法解決OR邏輯的分類。 import numpy as np import matplotlib.pyplot as

Angular 4 - The Basics 筆記1: Install

install rst logs nod first log 筆記 npm app Install Node.js Install Angular CLI sudo npm install -g @angular/cli Set-up new app

JAVA學習筆記1——a++與++a的區別

col int 演示 opera 解析 代碼 數據 ++i div 需求:此博客用於解釋i++與++i的區別。 過程: 1、名稱解釋 ++:自增,即在原有數據基礎上+1,再賦給原有數據。 2、程序演示 (1)代碼: 1 class OperateDemo 2 { 3

vray學習筆記1

com .cn 過程 分組 是把 皮膚 mon image 基本 vray是個什麽東西? 它是個渲染器。 渲染器是個什麽東西? 渲染器就是3d軟件裏面把模型畫成一張圖片的東西,渲染的過程就是把3D物體變成2D畫面的過程。 模型是個什麽東西? 模型就是模型,它由兩部分組成,第

Python學習手冊筆記1:Python對象類型

python 在Python中一切皆對象,Python程序可以分解為模塊、語句、表達式及對象。如下所示:1 程序由模塊組成2 模塊包含語句3 語句包含表達式4 表達式建立並處理對象 內置對象(核心類型):1)數字:>>> 2+2 #整數加法4>>&g

《深入理解C指針》學習筆記1--- 指針之外

結構 def form 學習 編程 stdlib.h struct 一個 char   C語言從誕生之初就非常善於和硬件打交道,經過這麽多年的發展之後,其靈活性和超強的特征是受到幾乎所有程序員的肯定。C語言的這種靈活性很大一部分程度來源與C指針,指針為C語言動態操控內存提供