1. 程式人生 > 其它 >Java 響應式程式設計系列---Reactor 入門總結

Java 響應式程式設計系列---Reactor 入門總結

技術標籤:其它

一、概述

資料離散化是一個非常重要的思想。

為什麼要離散化?當以權值為下標的時候,有時候值太大,存不下。 所以把要離散化的每一個數組裡面的數對映到另一個值小一點的數組裡面去。

打個比方,某個題目告訴你有10^4個數,每個數大小不超過10^10,要你對這些數進行操作,那麼肯定不能直接開10^10大小的陣列,但是10^4的範圍就完全沒問題。

我們來看一下定義:離散化,把無限空間中有限的個體對映到有限的空間中去,以此提高演算法的時空效率。(by百度百科)

通俗的說,離散化是在不改變資料相對大小的條件下,對資料進行相應的縮小例如:

原資料:1,999,100000,15;處理後:1,3,4,2;

原資料:{100,200},{20,50000},{1,400};

處理後:{3,4},{2,6},{1,5};

但是離散化僅適用於只關注元素之間的大小關係而不關注元素本身的值!

二、原理與操作

假如你只想簡單操作一下,如求個逆序對什麼的,那直接排序後將它的順序覆蓋回去就可以啦。(它不能去重)

假如你想寫的更加專業就要採用以下步驟:

1、排序

2、去重

3、索引

首先我們要對所要進行離散化的資料進行排序:一般使用sort對陣列或結構體排序。

然後是去重操作,為了寫出高效的程式碼,我們需要複習兩個STL函式:unique()和lower_bound(),他們同時隸屬於#include<algorithm>。

unique的作用是“去掉”容器中相鄰元素的重複元素(不一定要求陣列有序),它會把重複的元素新增到容器末尾(所以陣列大小並沒有改變),而返回值是去重之後的尾地址;

函式lower_bound()在first和last中的前閉後開區間進行二分查詢,返回大於或等於val的第一個元素位置。如果所有元素都小於val,則返回last的位置。

【ps.upper_bound是返回第一個大於b[x]的指標,upper_bound()=lower_bound()+1】

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int lsh[1000], lshcopy[1000], sy[1000]; //lsh[n]是即將被離散化的陣列,lshcopy[n]是a[n]的副本,sy[n]用於排序去重後提供離散化後的值
int main()
{
	int n;	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&sy[i]);
		lshcopy[i]=sy[i];
			
	} 
	sort(sy,sy+n);//第一步排序 
    int size=unique(sy,sy+n)-sy;//unique顯示去重後的個數 
    printf("size is : %d",size);
    printf("\n");
    for(int i=0;i<n;i++)
    {
    	lsh[i]=lower_bound(sy,sy+size,lshcopy[i])-sy; //即lsh[i]為lshcopy[i]離散化後對應的值  
		printf("lsh is : %d",lsh[i]);  	
	}
 
}

三、應用

題目

題目連結

AcWing的OJ,https://www.acwing.com/problem/content/804/

我的OJ,http://47.110.135.197/problem.php?id=5239

題目描述

假定有一個無限長的數軸,數軸上每個座標上的數都是 0。
現在,我們首先進行 n 次操作,每次操作將某一位置 x 上的數加 c。
近下來,進行 m 次詢問,每個詢問包含兩個整數 l 和 r,你需要求出在區間 [l, r] 之間的所有數的和。

輸入

第一行包含兩個整數 n 和 m。
接下來 n 行,每行包含兩個整數 x 和 c。
再接下里 m 行,每行包含兩個整數 l 和 r。

輸出

共 m 行,每行輸出一個詢問中所求的區間內數字和。

樣例輸入


  1. 3 3

  2. 1 2

  3. 3 6

  4. 7 5

  5. 1 3

  6. 4 6

  7. 7 8

樣例輸出


  1. 8

  2. 0

  3. 5

資料範圍

−10^9 ≤ x ≤ 10^9,
1 ≤ n, m ≤ 10^5,
−10^9 ≤ l ≤ r ≤ 10^9,
−10000 ≤ c ≤ 10000

分析

資料範圍分析

第一眼看到題目的時候,覺得題目好簡單,就是一個標準的字首和模板題。但是看到資料範圍後,才發現好多坑。

第一,有一個無限長的數軸,數軸上每個座標上的數都是 0。我們看到 x 的範圍是 [−10^9, 10^9] ,也就是 2*10^9 大小,如果直接定義這麼大的陣列,估計要爆。

第二,實際操作的資料是 n,n 的範圍是 [1, 10^5],也就是進過了 n 次操作後,需要插入的資料包括 n 個座標和 m 個 [l, r],因此最多有 3*10^5 個數據是非零的,其他資料都是 0。3*10^5 對比 2*10^9,我們可以發現,非常符合離散化的前提。

第三,由於事前不知道 n 的大小,可以使用 C++ STL 中的 vector,而不是定義陣列。

樣例資料分析

從原始資料中,我們知道 n=3,m=3。

第一次操作為:x=1,c=2,也就是將座標為 1 的資料加上 2,那麼該點對應的資料變為 2。

第二次操作為:x=3,c=6,也就是將座標為 3 的資料加上 6,那麼該點對應的資料變為 6。

第三次操作為:x=7,c=5,也就是將座標為 7 的資料加上 5,那麼該點對應的資料變為 5。

經過 3 此操作後,我們可以得到當前資料如下圖所示。

接下來,我們要進行 m 次區間和操作。

第一次區間為:l=1,r=3。如上圖所示,自然對應區間和為 2+6=8。

第二次區間為:l=4,r=6。如上圖所示,自然對應區間和為 0。

第三次區間為:l=7,r=8。如上圖所示,自然對應區間和為 5。

演算法思路

通過上面的分析,我們知道整個操作分兩部分,第一部分是加法操作(將某一位置 x 上的數加 c);第二部分是查詢操作(詢問區間 [l, r] 的區間和)。因此整個演算法的基本思路如下:

1、首先讀入所有操作資料。

2、離散化資料。將 n 次操作的座標,m 次查詢的 [l, r] 進行離散化。

3、將離散化後的資料進行 n 次加法運算。

4、求離散化後資料字首和。

5、將 m 次查詢的區間和輸出。注意 l 和 r 都需要對應到離散化資料。

注意:所有的資料都需要進行離散化。

為了加深對演算法的理解,我們利用樣例輸入,寫出整個資料的變化過程。

加入到 vector 後的資料如下,注意將所有的座標加入到 vector 中。

1 3 7 1 3 4 6 7 8

排序後的資料變為

1 1 3 3 4 6 7 7 8

去重後的資料變為

1 3 4 6 7 8

加法操作後,離散化後資料變為

0 2 6 0 0 5

離散化後字首和為

0 2 8 8 8 13

第一次查詢區間為:l=1,r=3。對應的離散化區間為 [1, 2],自然對應區間和為 8-0=8。

第二次查詢區間為:l=4,r=6。對應的離散化區間為 [3, 4],自然對應區間和為 8-8=0。

第三次查詢區間為:l=7,r=8。對應的離散化區間為 [5, 6],自然對應區間和為 13-8=5。

AC 參考程式碼

STL 版本

需要使用到 STL 的 vector、pair 資料型別,以及演算法庫中的 sort()、unique(),vector 的 earse()。

#include <bits/stdc++.h> 
using namespace std; 
const int MAXN = 3e5+4;
int lsh[MAXN];//離散化後的資料
int sum[MAXN];//字首和 
int main() {
    int n,m,i;
    cin >> n >> m; 
    vector< int > nums;//所有需要離散化資料
    vector< pair<int, int> > add;//加法操作
    vector< pair<int, int> > query;//查詢操作
    //讀入所有加法操作
    for (i=0; i<n; i++) {
        int x, c;
        cin >> x >> c;
        add.push_back({x, c});
        nums.push_back(x);//x需要離散化
    }
    //讀入查詢操作
    for (i=0; i<m; i++) {
        int l,r;
        cin >> l >> r;
        query.push_back({l, r});
        nums.push_back(l);
        nums.push_back(r);
    } 
    //排序
    sort(nums.begin(), nums.end());
    //去重
    nums.erase(unique(nums.begin(), nums.end()), nums.end()); 
    //處理加法
    for (auto item:add) {
        int x = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        lsh[x]+=item.second;
    }
 
    //求字首和
    for (i=1; i<=nums.size(); i++) {
        sum[i]=sum[i-1]+lsh[i];
    }
 
    //查詢字首和
    for (auto item:query) {
        int l = lower_bound(nums.begin(), nums.end(), item.first)-nums.begin()+1;
        int r = lower_bound(nums.begin(), nums.end(), item.second)-nums.begin()+1;
        cout << sum[r]-sum[l-1] << endl;
    } 
    return 0;
}

特別注意:由於使用到了字首和,陣列要下標從 1 開始計算,因此使用 STL 的 lower_bound() 查詢二分查詢左邊界的時候,需要將返回值增加 1。