基礎資結

Basic Data Structure

by PolarisChiba

講師介紹

姓名:李昕威

PolarisChiba

清大準大一生

 

就是他

課程流程

  1. STL

  2. 並查集、BIT、線段樹

  3. 額外:稀疏表

  1. 某資料結構介紹

  2. 例題演練

  3. 額外延伸(如果有的話)

方法vs函式

method V.S function

方法函式大考驗

  • 蔣立元在跳舞 
  • IonCamp受理報名 
  • 蔣立元和黃恩明報名參加IonCamp 

(方法)

(方法)

(函式)

方法與函式

  • 方法:描述一個物件的行為與能做的事
  • 函式:一塊程式碼,用來重複呼叫的

題外話

其實方法在C++中叫做member function

在JAVA中才叫做method

emplace V.S push

push:複製一個再丟進去

emplace:把數字放進去建構出一個來

vector<pair<int,int>> vec;
vec.push_back(make_pair(1, 2));
vec.emplace_back(1, 2);

Queue佇列

你說你不懂怎麼排隊?上這堂課就對了

#include<queue>
using namespace std;

queue<int> q;

先進先出(FIFO)

emplace

q.emplace(2);

front

q.front();

pop

q.pop();

總結

#include<queue>
using namespace std;

queue<int> q;
q.emplace(7122);
q.front();
q.pop();
  1. 為甚麼叫做front?

  2. 為甚麼不叫做IONthe1st?

  3. 為甚麼我不能愛叫就叫甚麼?

  4. 我可以自己為它取名嗎?

Stack堆疊

你說你不懂怎麼放盤子?上這堂課就對了

#include<stack>
using namespace std;

stack<int> s;

後進先出(LIFO)

emplace

s.emplace(2);

top

s.top();

pop

s.pop();

總結

#include<stack>
using namespace std;

stack<int> s;
s.emplace(7122);
s.top();
s.pop();

例題演練

Parentheses Balance

Parentheses Balance

(【【】(【】)()】)

給一個括號序列,請問其是否是合法的?

  1. 空字串是合法的
  2. A是合法的的話,(A)和[A]也是合法的
  3. A、B是合法的話,AB也是合法的

Parentheses Balance

【【】(【】)()】)

stack:

Parentheses Balance

【】(【】)()】)

(【

stack:

Parentheses Balance

】(【】)()】)

(【【

stack:

Parentheses Balance

(【】)()】)

(【

stack:

Parentheses Balance

【】)()】)

(【(

stack:

Parentheses Balance

】)()】)

(【(【

stack:

Parentheses Balance

)()】)

(【(

stack:

Parentheses Balance

()】)

(【

stack:

Parentheses Balance

)】)

(【(

stack:

Parentheses Balance

】)

(【

stack:

Parentheses Balance

stack:

Parentheses Balance


bool check( string line ) {
    stack<char, vector<char>> sk;
    for (auto x : line) {
        if ( x == '(' || x == '[' ) sk.emplace(x);
        else if ( x == ')' || x == ']' ) {
            if ( sk.empty() ) return false;
            char top = s.top();
            sk.pop();
            if ( top != x ) return false;
        }
    }
    return true;
}

deque雙向佇列

你說你想當個排隊達人?上這堂課就對了

#include<deque>
using namespace std;

deque<int> d;

emplace_front

d.emplace_front(2);

pop_front

d.pop_front();

總結

#include<deque>
using namespace std;
deque<int> d;
d.resize(10);
d.push_back(7122);
d.back();
d.pop_back();
d.push_front(7122);
d.front();
d.pop_front();
d[3] = 7122;

例題演練

Sliding Window(簡化版)

Sliding Window(簡化版)

7 1 2 2 71 22

維護未來有可能成為答案的那些數字

(k = 3 為例)

Sliding Window(簡化版)

 1 2 2 71 22

deque:7

Sliding Window(簡化版)

7 1 2 2 71 22

deque:7 1

Sliding Window(簡化版)

7 1 2 2 71 22

deque:7 2

Sliding Window(簡化版)

7 1 2 2 71 22

deque:2 2

Sliding Window(簡化版)

7 1 2 2 71 22

deque:71

Sliding Window(簡化版)

7 1 2 2 71 22

deque:71 22

Sliding Window(簡化版)

vector<int> solve( vector<int> a, size_t k ) {
    vector<int> b;
    deque<size_t> dq;
    
    for (size_t i = 0, n = a.size(); i < n; ++i) {
    	while ( !dq.empty && a[i] > a[dq.back()] ) 
            dq.pop_back();
        dq.emplace_back(i);
        while( dq.front() <= i - k ) dq.pop_front(); 
        // 刪掉不符合的
        // 這題先刪、還是後刪過期的都可以
        if ( i > k - 2 ) b.emplace_back( a[dq.front()] ); 
        //答案
    }
    return b;
}

單調隊列

定義:維護一個有單調性的序列

應用:DP區間極值、DP優化

例題:求一個序列中所有長度為K的連續子序列的區

   間最大值的最小值

 

Priority Queue優先佇列

你說想你當個比大小大師?上這堂課就對了

#include<queue>
using namespace std;

priority_queue<int> pq;

Heap堆積

你說想你當個比大小大師?讓我們先成為堆積木大師吧

這一棵樹有甚麼性質呢?

蔣立元

上方的元素都比下方大

這一棵樹有甚麼性質呢?

Top:71

這一棵樹有甚麼性質呢?

Push:27

這一棵樹有甚麼性質呢?

Push:27

這一棵樹有甚麼性質呢?

Pop:71

這一棵樹有甚麼性質呢?

Pop:71

這一棵樹有甚麼性質呢?

Pop:71

這一棵樹有甚麼性質呢?

Pop:71

這一棵樹有甚麼性質呢?

Pop:71

不同的Heap

  • Min-Heap:維護最小值的堆積

  • Max-Heap:維護最大值的堆積

top

pq.top();

emplace

pq.emplace(27);

emplace

pq.emplace(27);

pop

pq.pop();

pop

pq.pop();

pop

pq.pop();

總結

#include<queue>
using namespace std;

priority_queue<int> pq;
pq.emplace(7122);
pq.top();
pq.pop();

例題演練

中位數

中位數

中位數

中位數

中位數

中位數

if ( mh.empty() || mh.top() > i ) mh.emplace(i);
else Mh.emplace(i);

if (mh.size() > Mh.size() + 1) {
    Mh.emplace(mh.top());
    mh.pop();
}

if (Mh.size() < mh.size() + 1) {
    mh.emplace(Mh.top());
    Mh.pop();
}

為甚麼沒有???

你說你找不到你的祖父的手機?說不定那根本不存在

priority_queue<int> pq;
pq.clear(); // Compile Error!

Priority Queue?

Priority Queue挖挖挖

Priority Queue?

Priority Queue放大鏡

Priority Queue?

Priority Queue大解密

Stack?

Stack大解密

Queue?

Queue大解密

Deque?

Deque大解密

why?

資料結構:Priority Queue、Stack、Queue

容器  :vector、deque

Stack?

Stack大解密

效能較高的他們

stack<int, vector<int>> s;
queue<int, vector<int>> q;
priority_queue<int, vector<int>> pq;

less<int>

greater<int>

stack<int, vector<int>> s;
queue<int, vector<int>> q;
priority_queue<int, vector<int>> pq;
priority_queue<int, vector<int>, less<int>> pq1; // top最大,預設
priority_queue<int, vector<int>, greater<int>> pq2; // top最小

Set集合

你認為人類有很多地球嗎?不,我們並沒有

#include<set>
using namespace std;

set<int> s;

Red–Black Tree紅黑樹

地球上有很多棵樹,其中有一棵......

Red–Black Tree紅黑樹

How does red-black tree works?

  1. 各ノードは赤か黒の色をもつ。 
  2. 根は黒である (この条件はしばし省かれる。根は赤から黒に変えることはできるので、解析にはほとんど影響しない)。
  3. 葉 (NIL) はすべて黒である。葉はすべて根と同じ色である。
  4. 赤のノードは黒ノードを 2つ子に持つ(したがって、赤のノードの親ノードは黒である)。
  5. 任意のノードについて、そのノードから子孫の葉までの道に含まれる黒いノードの数は、選んだ葉によらず一定である(この条件は、「根から葉までの道に含まれる黒いノードの数は、葉によらず一定である」と言い換えることができる)。

好像有點複雜餒

你說你也這麼覺得?

Binary Search Tree

二元搜尋樹

二元搜尋樹

  1. 左邊的都比根小
  2. 右邊的都比根大
  3. 根和根一樣大
set<int> s;

find

s.find(17);

insert

s.insert(25);

erase

s.erase(17);

begin

s.begin();

rbegin

s.rbegin();

總結

#include<set>
using namespace std;

set<int> s;
s.insert(7122);

s.begin(); // pointer
*s.rbegin(); // integer
s.end(); // NULL

s.erase(7122); 
s.erase(s.begin());
s.erase(s.find(7122));

額外延伸

multiet重集

你認為人類有很多地球嗎?......可能真的有唷

multiset

multiset<int> s;

由大到小

set<int, greater<int>> s;

簡單圖判定

給定N個點和M條邊,請問這張圖是不是簡單圖?

不包含重邊

不包含自環

簡單圖判定

bool check(vector<pair<int,int>> &v) {
    set<pair<int,int>> s;
    for (auto i:v) {
    	if (i.first == i.second) return false;
        s.insert(i);
    }
    if (v.size() != s.size()) return false;
    return true;
}

Map映射

你說你無法將朋友的臉和名字對上嗎?來上這堂課就對了

#include<map>
using namespace std;

map<int, string> m;

Binary Search Tree

二元搜尋樹

題外話,競賽中朋友、同學很重要

map映射

從數字到字串的映射

map映射

從字串到數字的映射

map映射

map挖挖挖

erase

m.erase("CSY");

insert

m["joylintp"] = 1;

修改

m["joylintp"] = -1;

總結

#include<map>
using namespace std;

map<string, int> m;
m["CSY"] = 1;
m.erase(m.find("CSY"));

m.begin(); // pointer
*m.rbegin(); // pair
m.begin()->first // string
m.rbegin()->second // int

題外話 : /

例題演練

動態眾數問題

動態眾數問題

每次加入一個數字,問目前的眾數是誰?

如果有多個的話請輸出最大的

ex:1, 3, 2, 5, 3, 2, 3, 2, 2, 1, 5

ex:1, 3, 3, 5, 3, 3, 3, 3, 2, 2, 2

動態眾數問題

void mode(vector<int> &v) {
    map<int,int> m;
    int ans;
    for (auto i:v) {
    	m[i]++;
        if (m[ans] < m[i]) ans = i;
        else if (m[ans] == m[i] && ans < i) ans = i;
        cout << i << endl;
    }
}

雜湊表

你嫌你不夠快嗎?來上這堂課就對了

unordered_map<int, string> m;
unordered_set<int> s;
unordered_multiset<int> ss;

もっと、速く

hash碰撞

雜湊:將資料打亂混和

STL例題演練

如果時間不夠的話,就會先換到下個章節

給你 N 個數字,每次你可以選兩個數字 X、Y ,並花費 X+Y 元將兩者合併變成 X+Y ,請問最少要花多少元才能合併到只剩一個數字?

手續費(fee)

把所有數字丟進 Priority Queue 中

並每次取出最小的兩個

再把它們相加後放回去

是不是很想要每次取出最小的兩個相加?

手續費(fee)

手續費(fee)

int fee(vector<int> &v) {
    priority_queue<int, vector<int>, greater<int>> pq;
    for (auto i:v) pq.emplace(i);
    int ans = 0;
    while (pq.size() >= 2) {
    	int u = pq.top(); pq.pop();
        int t = pq.top(); pq.pop();
        ans += u + t;
        pq.emplace(u + t);
    }
    return ans;
}

後序運算式(簡化版)

給你一個後序運算式,請求出這個式子的結果。

範例:6 3 / 1 4 - * 3 + 8 - ­ 請輸出 ­11

(假設所有數字都小於等於九、且為正整數)

後序運算式(簡化版)

void sol(string s) {
    vector<int> v;
    for (auto i:s) {
    	if ('0' <= i && i <= '9') v.push_back(i - '0');
        else {
            int a = v.back(); v.pop_back();
            int b = v.back(); v.pop_back();
            if (i == '+') v.push_back(a + b);
            if (i == '-') v.push_back(a - b);
            if (i == '*') v.push_back(a * b);
            if (i == '/') v.push_back(a / b);   
        }
    }
    return v.back();
}

你講那麼快,

STL 的東西又那麼多,

我怎麼可能記得住?

中場休息

C++ reference

http://en.cppreference.com

中場休息

中場休息

中場休息

英文好難QAQ

中場休息

Disjoint Set 並查集

你知道你的爺爺的堂兄的父親不一定是

你隔壁同學的父親的表弟的祖父的哥哥的兒子嗎?

//disjoint set要自己手寫
//因為STL並沒有支援

Disjoint 並查集

一開始每個人的老大都是自己

Disjoint 並查集

某一天 22 號打贏 1 號

Disjoint 並查集

但隔天 22 號卻輸給了7122

Disjoint 並查集

過了幾天,變成了這樣

find

int find(int x) {
    if (x == boss[x]) return x;
    else return find(boss[x]);
}

unite

void unite(int x, int y) {
    boss[y] = x;
}

糟糕的情況...

此乃線性

好的情況

啟發式合併(其他技巧會教)

unite

void unite(int x, int y) 
{ 
    x = boss[x];
    y = boss[y];
    if (x == y) return;
    
    if (size[x] < size[y]) swap(x, y);
    boss[y] = x;
    size[x] += y;
}

更好的情況

路徑壓縮

find

int find(int x) {
    if (x == boss[x]) return x;
    boss[x] = find(boss[x]);
    return boss[x];
}

總結

int boss[N], size[N];
int find(int x) {
    return x == boss[x] ? x : boss[x] = find(boss[x]);
}
bool same(int x, int y) {
    return find(x) == find(y);
}
void unite(int x, int y) {
    x = find(x), y = find(y);
    if ( same(x, y) ) return;
    if ( size[x] < size[y] ) swap(x, y);
    boss[y] = x;
    size[x] += size[y];
}

例題演練

可愛的小動物

可愛的小動物

朋友的敵人是敵人

朋友的朋友是朋友

敵人的朋友是敵人

敵人的敵人是朋友

不能矛盾

可愛的小動物

如果只有朋友的話......

可愛的小動物

但是這個題目中有敵人...

可愛的小動物

如果只有朋友的話可以用並查集去維護

那敵人呢?

可愛的小動物

把朋友丟在黑色圈圈裡

把敵人丟到紫色圈圈裡

可愛的小動物

同個圈圈中的紫色和黑色是敵人

同個圈圈中的紫色和紫色是朋友

同個圈圈中的黑色和黑色是朋友

不同圈圈間沒什麼關係

可愛的小動物

紫色圈圈裡的數字可以當作 i + N

可愛的小動物

int aa = find(a), bb = find(b);
int ra = find(a + N), rb = find(b + N);

//a、b朋友
if (aa == rb) cout << "angry" << endl;
unite(a, b), unite(ra, rb);

// a、b敵人
if (aa == bb) cout << "angry" << endl;
unite(aa, rb), unite(bb, ra);

// a、b朋友嗎?
if (aa == bb) cout << "yeap" << endl;
else cout << "nope" << endl;

//a、b敵人嗎?

if (aa == rb) cout << "yeap" << endl;
else cout <<< "nope" << endl;

Segment Tree 線段樹

一般人在資料結構都會在這關放棄

你,能夠撐過這一關嗎?

//Segment Tree要自己手寫
//因為STL並沒有支援

雖然也有人因此中毒

就是了......

Segment Tree 線段樹

將序列不斷切一半

Segment Tree 線段樹

儲存區間的答案

Segment Tree 線段樹

查詢區間的答案

Segment Tree 線段樹

查詢區間的答案

int seg[4 * N + 1];
int query(int id, int l, int r, int ql, int qr) {
    if (qr < l || r < ql) return 0;
    if (ql <= l && r <= qr) return seg[id];
    int m = (l + r) / 2;
    int v1 = query(id * 2, l, m, ql, qr);
    int v2 = query(id * 2 + 1, m + 1, r, ql, qr);
    return v1 + v2;
}

Segment Tree 線段樹

修改單點的值

Segment Tree 線段樹

修改單點的值

void update(int id, int l, int r, int i, int v) {
    if (l == r) {
    	seg[id] += v;
        return;
    }
    int m = (l + r) / 2;
    if (i <= m) update(id * 2, l, m, i, v);
    else update(id * 2 + 1, m + 1, r, i, v);
    seg[id] = seg[id * 2] + seg[id * 2 + 1];
}
  1. 將區間的值都改成X
  2. 查詢區間的值

Segment Tree 線段樹

Segment Tree 線段樹

修改區間的值,懶惰標記

Segment Tree 線段樹

修改區間的值,懶惰標記

tag[4 * N + 1];

Segment Tree 線段樹

路過就推一下,將懶惰標記向下推

Segment Tree 線段樹

將懶惰標記向下推

void push(int id, int l, int r) {
    tag[id * 2] = tag[id];
    tag[id * 2 + 1] = tag[id];
    seg[id] = tag[id] * (r - l + 1);
    tag[id] = 0;
}

Segment Tree 線段樹

修改區間的答案

void update(int id, int l, int r, int ql, int qr, int x) {
    push(id, l, r);
    if (r < ql || qr < l) return;
    if (ql <= l && r <= qr) {
    	tag[id] = x;
        push(id, l, r);
        return;
    }
    int m = (l + r) / 2;
    update(id * 2, l, m, ql, qr, x);
    update(id * 2 + 1, m + 1, r, ql, qr, x);
    seg[id] = seg[id * 2] + seg[id * 2 + 1];
}

Segment Tree 線段樹

查詢區間的答案

int query(int id, int l, int r, int ql, int qr) {
    push(id, l, r);	
    if (qr < l || r < ql) return 0;
    if (ql <= l && r <= qr) return seg[id];
    int m = (l + r) / 2;
    int v1 = query(id * 2, l, m, ql, qr);
    int v2 = query(id * 2 + 1, m + 1, r, ql, qr);
    return v1 + v2;
}

如果是區間加X呢?

  1. 查詢區間加總

  2. 將區間所有數字加上一個數字X

區間加上X?

將懶惰標記向下推

void push(int id, int l, int r) {
    tag[id * 2] += tag[id];
    tag[id * 2 + 1] += tag[id];
    seg[id] += tag[id] * (r - l + 1);
    tag[id] = 0;
}

區間加上X?

修改區間的答案

void update(int id, int l, int r, int ql, int qr, int x) {
    push(id);
    if (r < ql || qr < l) return;
    if (ql <= l && r <= qr) {
    	tag[id] += x;
        push(id);
        return;
    }
    int m = (l + r) / 2;
    update(id * 2, l, m, ql, qr, x);
    update(id * 2 + 1, m + 1, r, ql, qr, x);
    seg[id] = seg[id * 2] + seg[id * 2 + 1];
}

例題演練

RMQ

RMQ

給你一個數列,請動態支援以下兩種操作

  1. 詢問區間最大值

  2. 將一個區間所有數字修改成V

RMQ push

void push(int id) {
    tag[id * 2] = tag[id];
    tag[id * 2 + 1] = tag[id];
    seg[id] = tag[id];
    tag[id] = 0;
}

RMQ query

int query(int id, int l, int r, int ql, int qr) {
    push(id);
    if (qr < l || r < ql) return 0;
    if (ql <= l && r <= qr) return seg[id];
    int m = (l + r) / 2;
    int v1 = query(id * 2, l, m, ql, qr);
    int v2 = query(id * 2 + 1, m + 1, r, ql, qr);
    return max(v1, v2);
}

RMQ update

void update(int id, int l, int r, int ql, int qr, int x) {
    push(id);
    if (r < ql || qr < l) return;
    if (ql <= l && r <= qr) {
    	tag[id] = x;
        push(id);
        return;
    }
    int m = (l + r) / 2;
    update(id * 2, l, m, ql, qr, x);
    update(id * 2 + 1, m + 1, r, ql, qr, x);
    seg[id] = max( seg[id * 2], seg[id * 2 + 1] );
}

BIT樹狀樹組

你說你不想寫線段樹?那就寫BIT吧

//Binary Indexed Tree要自己手寫
//因為STL並沒有支援

二進位制

1:0001

2:0010

3:0011

4:0100

5:0101

6:0110

7:0111

8:1000

BIT 樹狀樹組

樹狀樹組的長相

BIT 樹狀樹組

樹狀樹組查詢

BIT 樹狀樹組

樹狀樹組修改

舉個例子

2,5,1,4,7,10,2,6

單點加值

2,12,1,4,7,10,2,6

單點加值

update

int bit[N];
void update(int i, int x) {
    while (i <= N) {
    	bit[i] += x;
        i += i & -i;
    }
}

Lowit Bit

1:0001:0001

2:0010:0010

3:0011:0001

4:0100:0100

5:0101:0001

6:0110:0010

7:0111:0001

8:1000:1000

二進位制,最小的一個 1-bit 的值

區間(前綴)查詢

query

2,12,1,4,7,10,2,6

區間(前綴)查詢

query

int query(int i) {
    int res = 0;
    while (i > 0) {
    	res += bit[i];
        i -= i & -i;
    }
    return res;
}

區間查詢

將兩個前綴相減

如何單點改值?

把第二格改成12

2,5,1,4,7,10,2,6

如何單點改值?

等價於把第二格加上7

2,12,1,4,7,10,2,6

例題演練

逆序數對

逆序數對

給定一個數列,求有多少個(i, j)數對滿足:

i < j \\ a_i > a_j

逆序數對

檢查這個元素前面有多少元素在

逆序數對

檢查這個元素前面有多少元素在

//cnt[i]紀錄1~i總共出現在s中幾次
for(auto i:s) {
    ans += cnt[i - 1] - query(i - 1);
    update(i, 1);
}
cout << ans << '\n';

逆序數對

小提醒:

  1. 有可能會有重複的元素

  2. 有可能會超出int範圍

  3. 有可能有負數

額外延伸

二維BIT修改

int r, c, bit[N][N];
void update(int x, int y, int v) {
	while (x <= r) {
    	int t = y;
        while (y <= c) {
        	bit[x][y] += v;
            y += y & -y;
        }
        y = t;
        x += x & -x;
    }
}

二維BIT查詢

int query(int x, int y) {
	int res = 0;
    while (x > 0) {
    	int t = y;
        while (y > 0) {
        	res += bit[x][y];
            y -= y & -y;
        }
        y = t;
        x -= x & -x;
    }
    return res;
}

Sparse Table稀疏表

你說你不想寫BIT,那就寫Sparse Table吧

前提是對面不會動

//Sparse Table要自己手寫
//因為STL並沒有支援

Sparse Table 稀疏表

稀疏表的示意圖

Sparse Table 稀疏表

稀疏表的查詢

Sparse Table 稀疏表

int n, s[LOGN][N];
for(int j = 1; (1 << j) <= n; j++)
	for(int i = 0; i + (1 << j) <= n; i++)   
		s[j][i] = min( 
        	s[j - 1][i], 
        	s[j - 1][i + (1 << (j - 1) )] 
        );

int query(int l,int r) {
    int k = __lg(r - l + 1);
    return max( s[k][l], s[k][r - (1 << k) + 1] );
}

例題演練

桑京邀請賽

桑京邀請賽

給定一個序列長度為N,並有許多詢問

每次詢問一個區間中的最大值與最小值。

(空間只夠開 10N 個 int)

桑京邀請賽

對稀疏表做滾動

int s1[2500000], n, m;
int l[1000006], r[1000006];
bitset<1000006> ok;
int main(){
    scanf("%d %d",&n, &m);
    for(int i = 0 ; i < m ; i++)
    	scanf("%d %d", &l[i], &r[i]), l[i]--, --r[i];
    for(int i = 0 ; i < n ; i++) scanf("%d", &s1[i]);
        
    for(int i = 0 ; i < m ; i++)
    	if( lo(l[i], r[i]) == 0 )
        	l[i] = s1[ l[i] ], ok[i] = 1;    
    
    for(int i = 1; (1 << i) <= n ; i++){
        for(int j = 0 ; j + (1 << i) <= n ; j++)
            s1[j] = max( s1[j], s1[ j + ( 1 << (i - 1) ) ] );
        for(int j = 0 ; j < m ; j++)
            if( lo( l[j], r[j] ) == i && !ok[j] )
                l[j] = max( 
                	s1[ l[j] ], 
                	s1[ r[j] - ( 1 << i ) + 1 ] ), ok[j] = 1; 
    }
    for(int i = 0 ; i < m ; i++) printf("%d\n", l[i]);
}

非STL例題演練

如果沒時間的話就請請大家回去練習吧

食物鏈

有三種動物A, B, C,A會吃B、B會吃C、C會吃A

現在有N隻動物,分別屬於ABC其中一種,並有人給你許多它們之間的關係

  1. X與Y是同類
  2. X吃Y

但這個人有可能說謊,只要滿足以下就算這個人說謊

  1. X或Y大於N
  2. X吃X

請問這個人說了多少謊?

食物鏈

可愛小動物的三種關係的版本

詳細實作就請大家參考可愛小動物那一題

地雷區

在一張N X M的棋盤上,有些地方有地雷,一個地雷被引爆的話會炸到周圍3 X 3的區域;若兩個地雷的3 X 3區域有重疊的話,引爆其中一個的話另一個也會被連鎖引爆。

請問要人工引爆多少個地雷才能炸掉所有地雷?

地雷區

from TIOJ

地雷區

for (int t = 0; t < v.size(); ++t)
{
    pair<int,int> i = v[t];
    for(int dx = -2; dx <= 2; ++dx) for(int dy = -2; dy <= 2; ++dy)
    {
        int x = i.first + dx, y = i.second + dy;
        int d = lower_bound(all(v), make_pair(x,y)) - v.begin();
        if(v[d] != make_pair(x,y)) continue;
        d = find(d);
        int dd = find(t);
        if(dd != d) fa[d] = dd, ++cnt;
    }
}
cout << n - cnt << endl;

強者物質

有一個長度最多為 10^5 的數列,有正有負。

請問有多少區間的和為正的?

並請問區間和最大為多少?

第二個問題的答案是DP,就當作早上的課的練習請大家想想囉

強者物質

看到區間和,有個小技巧是換成前綴和來思考

區間和為正

等價於前綴和相減為正

等價於後面的前綴和比前面的前綴和大

計算多少個後面的元素比前面的大可以用BIT

強者物質

for (int i = 1; i <= n; ++i) {
	ans += (s[i] > 0);
    \\離散化
    s[i] = lower_bound(all(li), s[i]) - li.begin() + 1; 
    add(s[i], 1);
    ans += sum(s[i] - 1);
}
cout << ans << endl;

隕石

有一個無限長的數列,一開始所有數字都是零。

給 N 個區間 (L, R),要將那個區間的數字都加一。

現在你能刪掉 K 個區間,請問最後整個數列中的最大值最小可以是多少?

隕石

二分搜答案

將區間放在數線上,如果數線上有個點被太多線段覆蓋到的話,代表就要刪掉線段

要刪掉線段的話,刪掉右界最右邊的最好

隕石

pair<int,int> a[N];
set<int> s;

bool check(int mid)
{
    int res = k;
    s.clear(); // 儲存線段在數線上的資訊
    for (int i = 0; i < n; ++i) {
        while(s.size() && *s.begin() <= a[i].first) 
            s.erase(s.begin()); // 過期了
        s.insert(a[i].second);
        while(s.size() > mid) { //現在這個點被太多線段覆蓋
            s.erase((--s.end())); // 刪掉右界最右邊的
            --res; //我只能刪掉K個區間
        }
        if(res < 0) return false;
    }
    return true;
}

矩形覆蓋面積計算

給定座標平面上的許多平行於 X 軸與 Y 軸的矩形

請求出所有矩形的面積的聯集大小

矩形覆蓋面積計算

掃描線

矩形覆蓋面積計算

計算此時的 y 軸上有多少個被覆蓋

矩形覆蓋面積計算

宣告

int flag[4 * N + 1];
int count[4 * N + 1];

矩形覆蓋面積計算

pull

void pull(int id, int l, int r) {
    if ( flag[id] > 0 ) 
    	count[id] = r - l + 1;
    else count[id] = 
    	count[id * 2] + count[id * 2 + 1];
}

矩形覆蓋面積計算

Query

int query() {
    return count[1];	
}

矩形覆蓋面積計算

Update

void update(int id, int l, int r, int ql, int qr, int v) {
    if (qr < l || r < ql) return;
    if (ql <= l && r <= qr) {
    	flag[id] += v;
    }
    else {
    	int m = (l + r) / 2;
        update(id * 2, l, m, ql, qr, v);
        update(id * 2 + 1, m + 1, r, ql, qr, v);
    }
    pull(id, l, r);
}

如果,生命的腳印終有一天

會被時間的塵埃掩埋... ...

那我們就永遠不能

--停下腳步

FIN

2020IONCamp基礎資結

By polarischiba

2020IONCamp基礎資結

  • 696