日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【第二讲】数据结构

發布時間:2024/1/18 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【第二讲】数据结构 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

來自:算法基礎課

文章目錄

  • 第二講 數據結構
    • 2.1單鏈表
      • 2.1.1 826. 單鏈表
    • 2.2雙鏈表
      • 2.2.1 827. 雙鏈表
    • 2.3棧
      • 2.3.1 828. 模擬棧
      • 2.3.2 3302. 表達式求值
    • 2.4隊列
      • 2.4.1 829. 模擬隊列
    • 2.5單調棧
      • 2.5.1 830. 單調棧
    • 2.6單調隊列
      • 2.6.1 154. 滑動窗口
    • 2.7KMP
      • 2.7.1 831. KMP字符串
    • 2.8Trie
      • 2.8.1 835. Trie字符串統計
      • 2.8.2 143. 最大異或對
    • 2.9并查集
      • 2.9.1 836. 合并集合
      • 2.9.2 837. 連通塊中點的數量
      • 2.9.3 240. 食物鏈
    • 2.10堆
      • 2.10.1 838. 堆排序
      • 2.10.2 839. 模擬堆
    • 2.11哈希表
      • 2.11.1 840. 模擬散列表
      • 2.11.2 841. 字符串哈希
    • 2.12 STL

第二講 數據結構

2.1單鏈表

單鏈表 —— 模板題 AcWing 826. 單鏈表

// head存儲鏈表頭,e[]存儲節點的值,ne[]存儲節點的next指針,idx表示當前用到了哪個節點 int head, e[N], ne[N], idx;// 初始化 void init() {head = -1;idx = 0; }// 在鏈表頭插入一個數a void insert(int a) {e[idx] = a, ne[idx] = head, head = idx ++ ; }// 將頭結點刪除,需要保證頭結點存在 void remove() {head = ne[head]; }

2.1.1 826. 單鏈表

實現一個單鏈表,鏈表初始為空,支持三種操作:

向鏈表頭插入一個數;

刪除第 k 個插入的數后面的數;

在第 k 個插入的數后插入一個數。

現在要對該鏈表進行 M 次操作,進行完所有操作后,從頭到尾輸出整個鏈表。

注意:題目中第 k 個插入的數并不是指當前鏈表的第 k 個數。例如操作過程中一共插入了 n 個數,則按照插入的時間順序,這 n 個數依次為:第 1 個插入的數,第 2 個插入的數,…第 n 個插入的數。

輸入格式

第一行包含整數 M,表示操作次數。

接下來 M 行,每行包含一個操作命令,操作命令可能為以下幾種:

H x,表示向鏈表頭插入一個數 x。

D k,表示刪除第 k 個插入的數后面的數(當 k 為 0 時,表示刪除頭結點)。

I k x,表示在第 k 個插入的數后面插入一個數 x(此操作中 k 均大于 0)。

輸出格式

共一行,將整個鏈表從頭到尾輸出。

數據范圍

1≤M≤100000

所有操作保證合法。

輸入樣例:

10

H 9

I 1 1

D 1

D 0

H 6

I 3 6

I 4 5

I 4 5

I 3 4

D 6

輸出樣例:

6 4 6 5

#include<bits/stdc++.h> using namespace std; const int N=100010; int m; int head,e[N],ne[N],idx;void init() {head=-1;idx=1; }void add_head(int x) {e[idx]=x;ne[idx]=head;head=idx;idx++; }void add(int k,int x) {e[idx]=x;ne[idx]=ne[k];ne[k]=idx;idx++; }void myremove(int k) {if(k==0){head=ne[head];}else{ne[k]=ne[ne[k]];} } int main() {cin>>m;init();while(m--){char ch;cin>>ch;if(ch=='H'){int x;cin>>x;add_head(x);}else if(ch=='D'){int k;cin>>k;myremove(k);}else{int k,x;cin>>k>>x;add(k,x);}}int p=head;while(p!=-1){cout<<e[p]<<" ";p=ne[p];}return 0; }

2.2雙鏈表

雙鏈表 —— 模板題 AcWing 827. 雙鏈表

// e[]表示節點的值,l[]表示節點的左指針,r[]表示節點的右指針,idx表示當前用到了哪個節點 int e[N], l[N], r[N], idx;// 初始化 void init() {//0是左端點,1是右端點r[0] = 1, l[1] = 0;idx = 2; }// 在節點a的右邊插入一個數x void insert(int a, int x) {e[idx] = x;l[idx] = a, r[idx] = r[a];l[r[a]] = idx, r[a] = idx ++ ; }// 刪除節點a void remove(int a) {l[r[a]] = l[a];r[l[a]] = r[a]; }

2.2.1 827. 雙鏈表

實現一個雙鏈表,雙鏈表初始為空,支持 5 種操作:

在最左側插入一個數;

在最右側插入一個數;

將第 k 個插入的數刪除;

在第 k 個插入的數左側插入一個數;

在第 k 個插入的數右側插入一個數

現在要對該鏈表進行 M 次操作,進行完所有操作后,從左到右輸出整個鏈表。

注意:題目中第 k 個插入的數并不是指當前鏈表的第 k 個數。例如操作過程中一共插入了 n 個數,則按照插入的時間順序,這 n 個數依次為:第 1 個插入的數,第 2 個插入的數,…第 n 個插入的數。

輸入格式

第一行包含整數 M,表示操作次數。

接下來 M 行,每行包含一個操作命令,操作命令可能為以下幾種:

L x,表示在鏈表的最左端插入數 x。

R x,表示在鏈表的最右端插入數 x。

D k,表示將第 k 個插入的數刪除。

IL k x,表示在第 k 個插入的數左側插入一個數。

IR k x,表示在第 k 個插入的數右側插入一個數。

輸出格式

共一行,將整個鏈表從左到右輸出。

數據范圍

1≤M≤100000

所有操作保證合法。

輸入樣例:

10

R 7

D 1

L 3

IL 2 10

D 3

IL 2 7

L 8

R 9

IL 4 7

IR 2 2

輸出樣例:

8 7 7 3 2 9

#include<bits/stdc++.h> using namespace std; const int N=100010; int m; int e[N],l[N],r[N],idx; void init() {r[0]=1;l[1]=0;idx=2; }void add(int k,int x) {e[idx]=x;r[idx]=r[k];l[idx]=k;l[r[k]]=idx;r[k]=idx;idx++; }void myremove(int k) {r[l[k]]=r[k];l[r[k]]=l[k]; } int main() {cin>>m;init();while(m--){string op;int k,x;cin>>op;if(op=="L"){k=0;cin>>x;add(k,x);}else if(op=="R"){k=l[1];cin>>x;add(k,x);}else if(op=="D"){cin>>k;myremove(k+1);}else if(op=="IL"){cin>>k>>x;k=l[k+1];add(k,x);}else{cin>>k>>x;add(k+1,x);}}int p=r[0];while(p!=1){cout<<e[p]<<" ";p=r[p];}return 0; }

2.3棧

棧 —— 模板題 AcWing 828. 模擬棧

// tt表示棧頂 int stk[N], tt = 0;// 向棧頂插入一個數 stk[ ++ tt] = x;// 從棧頂彈出一個數 tt -- ;// 棧頂的值 stk[tt];// 判斷棧是否為空 if (tt > 0) {}

2.3.1 828. 模擬棧

實現一個棧,棧初始為空,支持四種操作:

push x – 向棧頂插入一個數 x;

pop – 從棧頂彈出一個數;

empty – 判斷棧是否為空;

query – 查詢棧頂元素。

現在要對棧進行 M 個操作,其中的每個操作 3 和操作 4 都要輸出相應的結果。

輸入格式

第一行包含整數 M,表示操作次數。

接下來 M 行,每行包含一個操作命令,操作命令為 push x,pop,empty,query 中的一種。

輸出格式

對于每個 empty 和 query 操作都要輸出一個查詢結果,每個結果占一行。

其中,empty 操作的查詢結果為 YES 或 NO,query 操作的查詢結果為一個整數,表示棧頂元素的值。

數據范圍

1≤M≤100000,

1≤x≤109

所有操作保證合法。

輸入樣例:

10

push 5

query

push 6

pop

query

pop

empty

push 4

query

empty

輸出樣例:

5

5

YES

4

NO

#include<bits/stdc++.h> using namespace std; int m; stack<int> sk; int main() {cin>>m;while(m--){string op;cin>>op;int x;if(op=="push"){cin>>x;sk.push(x);}else if(op=="pop"){sk.pop();}else if(op=="empty"){if(sk.empty()){cout<<"YES"<<endl;}else{cout<<"NO"<<endl;}}else{cout<<sk.top()<<endl;}}return 0; }

2.3.2 3302. 表達式求值

給定一個表達式,其中運算符僅包含 +,-,*,/(加 減 乘 整除),可能包含括號,請你求出表達式的最終值。

注意:

數據保證給定的表達式合法。

題目保證符號 - 只作為減號出現,不會作為負號出現,例如,-1+2,(2+2)*(-(1+1)+2) 之類表達式均不會出現。

題目保證表達式中所有數字均為正整數。

題目保證表達式在中間計算過程以及結果中,均不超過 231?1。

題目中的整除是指向 0 取整,也就是說對于大于 0 的結果向下取整,例如 5/3=1,對于小于 0 的結果向上取整,例如 5/(1?4)=?1。

C++和Java中的整除默認是向零取整;Python中的整除//默認向下取整,因此Python的eval()函數中的整除也是向下取整,在本題中不能直接使用。

輸入格式

共一行,為給定表達式。

輸出格式

共一行,為表達式的結果。

數據范圍

表達式的長度不超過 105。

輸入樣例:

(2+2)*(1+1)

輸出樣例:

8

#include<bits/stdc++.h> using namespace std;stack<int> num; stack<char> op;void eval() {int b=num.top();num.pop();int a=num.top();num.pop();char c=op.top();op.pop();int x;if(c=='+'){x=a+b;}else if(c=='-'){x=a-b;}else if(c=='*'){x=a*b;}else{x=a/b;}num.push(x); } int main() {map<char,int> mp;mp.insert(make_pair('+',1));mp.insert(make_pair('-',1));mp.insert(make_pair('*',2));mp.insert(make_pair('/',2));string str;cin>>str;for(int i=0;i<str.size();i++){char c=str[i];if(isdigit(c)){int x=0,j=i;while(j<str.size()&&isdigit(str[j])){x=x*10+str[j++]-'0';}i=j-1;num.push(x);}else if(c=='(') op.push(c);else if(c==')'){while(op.top()!='(') eval();op.pop();}else{while(op.size()&&op.top()!='('&&mp[op.top()]>=mp[c]) eval();op.push(c);}}while(op.size()) eval();cout<<num.top();return 0; }

2.4隊列

隊列 —— 模板題 AcWing 829. 模擬隊列

\1. 普通隊列:

// hh 表示隊頭,tt表示隊尾 int q[N], hh = 0, tt = -1;// 向隊尾插入一個數 q[ ++ tt] = x;// 從隊頭彈出一個數 hh ++ ;// 隊頭的值 q[hh];// 判斷隊列是否為空 if (hh <= tt) {}

\2. 循環隊列

// hh 表示隊頭,tt表示隊尾的后一個位置 int q[N], hh = 0, tt = 0;// 向隊尾插入一個數 q[tt ++ ] = x; if (tt == N) tt = 0;// 從隊頭彈出一個數 hh ++ ; if (hh == N) hh = 0;// 隊頭的值 q[hh];// 判斷隊列是否為空 if (hh != tt) {}

2.4.1 829. 模擬隊列

實現一個隊列,隊列初始為空,支持四種操作:

push x – 向隊尾插入一個數 x;

pop – 從隊頭彈出一個數;

empty – 判斷隊列是否為空;

query – 查詢隊頭元素。

現在要對隊列進行 M 個操作,其中的每個操作 3 和操作 4 都要輸出相應的結果。

輸入格式

第一行包含整數 M,表示操作次數。

接下來 M 行,每行包含一個操作命令,操作命令為 push x,pop,empty,query 中的一種。

輸出格式

對于每個 empty 和 query 操作都要輸出一個查詢結果,每個結果占一行。

其中,empty 操作的查詢結果為 YES 或 NO,query 操作的查詢結果為一個整數,表示隊頭元素的值。

數據范圍

1≤M≤100000,

1≤x≤109,

所有操作保證合法。

輸入樣例:

10

push 6

empty

query

pop

empty

push 3

push 4

pop

query

push 6

輸出樣例:

NO

6

YES

4

#include<bits/stdc++.h> using namespace std; int m; queue<int> q; int main() {cin>>m;while(m--){string op;cin>>op;int x;if(op=="push"){cin>>x;q.push(x);}else if(op=="pop"){q.pop();}else if(op=="empty"){if(q.empty()) cout<<"YES"<<endl;else cout<<"NO"<<endl;}else cout<<q.front()<<endl;}return 0; }

2.5單調棧

單調棧 —— 模板題 AcWing 830. 單調棧

常見模型:找出每個數左邊離它最近的比它大/小的數

int tt = 0; for (int i = 1; i <= n; i ++ ) {while (tt && check(stk[tt], i)) tt -- ;stk[ ++ tt] = i; }

2.5.1 830. 單調棧

給定一個長度為 N 的整數數列,輸出每個數左邊第一個比它小的數,如果不存在則輸出 ?1。

輸入格式

第一行包含整數 N,表示數列長度。

第二行包含 N 個整數,表示整數數列。

輸出格式

共一行,包含 N 個整數,其中第 i 個數表示第 i 個數的左邊第一個比它小的數,如果不存在則輸出 ?1。

數據范圍

1≤N≤105

1≤數列中元素≤109

輸入樣例:

5

3 4 2 7 5

輸出樣例:

-1 3 -1 2 2

#include<bits/stdc++.h> using namespace std; stack<int> sk; int n; int main() {cin>>n;for(int i=0;i<n;i++){int x;cin>>x;while(!sk.empty()&&sk.top()>=x) sk.pop();if(sk.empty()) cout<<-1<<" ";else cout<<sk.top()<<" ";sk.push(x);}return 0; }

2.6單調隊列

調隊列 —— 模板題 AcWing 154. 滑動窗口

常見模型:找出滑動窗口中的最大值/最小值

int hh = 0, tt = -1; for (int i = 0; i < n; i ++ ) {while (hh <= tt && check_out(q[hh])) hh ++ ; // 判斷隊頭是否滑出窗口while (hh <= tt && check(q[tt], i)) tt -- ;q[ ++ tt] = i; }

2.6.1 154. 滑動窗口

給定一個大小為 n≤106 的數組。

有一個大小為 k 的滑動窗口,它從數組的最左邊移動到最右邊。

你只能在窗口中看到 k 個數字。

每次滑動窗口向右移動一個位置。

以下是一個例子:

該數組為 [1 3 -1 -3 5 3 6 7],k 為 3。

?

你的任務是確定滑動窗口位于每個位置時,窗口中的最大值和最小值。

輸入格式

輸入包含兩行。

第一行包含兩個整數 n 和 k,分別代表數組長度和滑動窗口的長度。

第二行有 n 個整數,代表數組的具體數值。

同行數據之間用空格隔開。

輸出格式

輸出包含兩個。

第一行輸出,從左至右,每個位置滑動窗口中的最小值。

第二行輸出,從左至右,每個位置滑動窗口中的最大值。

輸入樣例:

8 3

1 3 -1 -3 5 3 6 7

輸出樣例:

-1 -3 -3 -3 3 3

3 3 5 5 6 7

#include<bits/stdc++.h> using namespace std; const int N=1000010; int n,k; int a[N],q[N]; int main() {cin>>n>>k;for(int i=0;i<n;i++){cin>>a[i];}int hh=0,tt=-1;for(int i=0;i<n;i++){if(hh<=tt&&i-k+1>q[hh]) hh++;while(hh<=tt&&a[q[tt]]>=a[i]) tt--;q[++tt]=i;if(i>=k-1){cout<<a[q[hh]]<<" ";}}cout<<endl;hh=0,tt=-1;for(int i=0;i<n;i++){if(hh<=tt&&i-k+1>q[hh]) hh++;while(hh<=tt&&a[q[tt]]<=a[i]) tt--;q[++tt]=i;if(i>=k-1){cout<<a[q[hh]]<<" ";}}cout<<endl;return 0; }

2.7KMP

KMP —— 模板題 AcWing 831. KMP字符串

// s[]是長文本,p[]是模式串,n是s的長度,m是p的長度 求模式串的Next數組: for (int i = 2, j = 0; i <= m; i ++ ) {while (j && p[i] != p[j + 1]) j = ne[j];if (p[i] == p[j + 1]) j ++ ;ne[i] = j; }// 匹配 for (int i = 1, j = 0; i <= n; i ++ ) {while (j && s[i] != p[j + 1]) j = ne[j];if (s[i] == p[j + 1]) j ++ ;if (j == m){j = ne[j];// 匹配成功后的邏輯} }

2.7.1 831. KMP字符串

給定一個模式串 S,以及一個模板串 P,所有字符串中只包含大小寫英文字母以及阿拉伯數字。

模板串 P 在模式串 S 中多次作為子串出現。

求出模板串 P 在模式串 S 中所有出現的位置的起始下標。

輸入格式

第一行輸入整數 N,表示字符串 P 的長度。

第二行輸入字符串 P。

第三行輸入整數 M,表示字符串 S 的長度。

第四行輸入字符串 S。

輸出格式

共一行,輸出所有出現位置的起始下標(下標從 0 開始計數),整數之間用空格隔開。

數據范圍

1≤N≤105

1≤M≤106

輸入樣例:

3

aba

5

ababa

輸出樣例:

0 2

#include<bits/stdc++.h> using namespace std; const int N=100010,M=1000010; int n,m; int ne[N]; char s[M],p[N]; int main() {cin>>n>>p+1;cin>>m>>s+1;for(int i=2,j=0;i<=n;i++){while(j&&p[i]!=p[j+1]) j=ne[j];if(p[i]==p[j+1]) j++;ne[i]=j;}for(int i=1,j=0;i<=m;i++){while(j&&s[i]!=p[j+1]) j=ne[j];if(s[i]==p[j+1]) j++;if(j==n){cout<<i-n<<" ";j=ne[j];}}return 0; }

2.8Trie

Trie樹 —— 模板題 AcWing 835. Trie字符串統計

int son[N][26], cnt[N], idx; // 0號點既是根節點,又是空節點 // son[][]存儲樹中每個節點的子節點 // cnt[]存儲以每個節點結尾的單詞數量// 插入一個字符串 void insert(char *str) {int p = 0;for (int i = 0; str[i]; i ++ ){int u = str[i] - 'a';if (!son[p][u]) son[p][u] = ++ idx;p = son[p][u];}cnt[p] ++ ; }// 查詢字符串出現的次數 int query(char *str) {int p = 0;for (int i = 0; str[i]; i ++ ){int u = str[i] - 'a';if (!son[p][u]) return 0;p = son[p][u];}return cnt[p]; }

2.8.1 835. Trie字符串統計

維護一個字符串集合,支持兩種操作:

I x 向集合中插入一個字符串 x;

Q x 詢問一個字符串在集合中出現了多少次。

共有 N 個操作,輸入的字符串總長度不超過 105,字符串僅包含小寫英文字母。

輸入格式

第一行包含整數 N,表示操作數。

接下來 N 行,每行包含一個操作指令,指令為 I x 或 Q x 中的一種。

輸出格式

對于每個詢問指令 Q x,都要輸出一個整數作為結果,表示 x 在集合中出現的次數。

每個結果占一行。

數據范圍

1≤N≤2?104

輸入樣例:

5

I abc

Q abc

Q ab

I ab

Q ab

輸出樣例:

1

0

1

#include<bits/stdc++.h> using namespace std; const int N=100010; int n; int son[N][26],cnt[N],idx=0; void _insert(string str) {int p=0;for(int i=0;i<str.length();i++){int u=str[i]-'a';if(!son[p][u]) son[p][u]=++idx;p=son[p][u];}cnt[p]++; }int query(string str) {int p=0;for(int i=0;i<str.length();i++){int u=str[i]-'a';if(!son[p][u]) return 0;p=son[p][u];}return cnt[p]; } int main() {cin>>n;while(n--){char op;string str;cin>>op>>str;if(op=='I'){_insert(str);}else{cout<<query(str)<<endl;}}return 0; }

2.8.2 143. 最大異或對

在給定的 N 個整數 A1,A2……AN 中選出兩個進行 xor(異或)運算,得到的結果最大是多少?

輸入格式

第一行輸入一個整數 N。

第二行輸入 N 個整數 A1~AN。

輸出格式

輸出一個整數表示答案。

數據范圍

1≤N≤105,

0≤Ai<231

輸入樣例:

3

1 2 3

輸出樣例:

3

#include<bits/stdc++.h> using namespace std; const int N=100010,M=3000010; int n; int a[N]; int son[M][2],idx;void _insert(int x) {int p=0;for(int i=30;i>=0;i--){int u=x>>i&1;if(!son[p][u]) son[p][u]=++idx;p=son[p][u];} }int query(int x) {int p=0,res=0;for(int i=30;i>=0;i--){int s=x>>i&1;if(son[p][!s]){res=res+(1<<i);p=son[p][!s];}else p=son[p][s];}return res; } int main() {cin>>n;for(int i=0;i<n;i++){cin>>a[i];_insert(a[i]);}int ans=0;for(int i=0;i<n;i++){ans=max(ans,query(a[i]));}cout<<ans;return 0; }

2.9并查集

并查集 —— 模板題 AcWing 836. 合并集合, AcWing 837. 連通塊中點的數量

(1)樸素并查集:

int p[N]; //存儲每個點的祖宗節點// 返回x的祖宗節點int find(int x){if (p[x] != x) p[x] = find(p[x]);return p[x];}// 初始化,假定節點編號是1~nfor (int i = 1; i <= n; i ++ ) p[i] = i;// 合并a和b所在的兩個集合:p[find(a)] = find(b);

(2)維護size的并查集:

int p[N], size[N];//p[]存儲每個點的祖宗節點, size[]只有祖宗節點的有意義,表示祖宗節點所在集合中的點的數量// 返回x的祖宗節點int find(int x){if (p[x] != x) p[x] = find(p[x]);return p[x];}// 初始化,假定節點編號是1~nfor (int i = 1; i <= n; i ++ ){p[i] = i;size[i] = 1;}// 合并a和b所在的兩個集合:size[find(b)] += size[find(a)];p[find(a)] = find(b);

(3)維護到祖宗節點距離的并查集:

int p[N], d[N];//p[]存儲每個點的祖宗節點, d[x]存儲x到p[x]的距離// 返回x的祖宗節點int find(int x){if (p[x] != x){int u = find(p[x]);d[x] += d[p[x]];p[x] = u;}return p[x];}// 初始化,假定節點編號是1~nfor (int i = 1; i <= n; i ++ ){p[i] = i;d[i] = 0;}// 合并a和b所在的兩個集合:p[find(a)] = find(b);d[find(a)] = distance; // 根據具體問題,初始化find(a)的偏移量

2.9.1 836. 合并集合

一共有 n 個數,編號是 1~n,最開始每個數各自在一個集合中。

現在要進行 m 個操作,操作共有兩種:

M a b,將編號為 a 和 b 的兩個數所在的集合合并,如果兩個數已經在同一個集合中,則忽略這個操作;

Q a b,詢問編號為 a 和 b 的兩個數是否在同一個集合中;

輸入格式

第一行輸入整數 n 和 m。

接下來 m 行,每行包含一個操作指令,指令為 M a b 或 Q a b 中的一種。

輸出格式

對于每個詢問指令 Q a b,都要輸出一個結果,如果 a 和 b 在同一集合內,則輸出 Yes,否則輸出 No。

每個結果占一行。

數據范圍

1≤n,m≤105

輸入樣例:

4 5

M 1 2

M 3 4

Q 1 2

Q 1 3

Q 3 4

輸出樣例:

Yes

No

Yes

#include<bits/stdc++.h> using namespace std; const int N=100010; int n,m; int p[N];int findP(int x) {if(p[x]!=x) p[x]=findP(p[x]);return p[x]; } int main() {for(int i=0;i<N;i++) p[i]=i;cin>>n>>m;while(m--){char op;int a,b;cin>>op>>a>>b;if(op=='M'){if(findP(a)!=findP(b)){p[findP(a)]=findP(b);}}else{if(findP(a)==findP(b)) cout<<"Yes"<<endl;else cout<<"No"<<endl;}}return 0; }

2.9.2 837. 連通塊中點的數量

給定一個包含 n 個點(編號為 1~n)的無向圖,初始時圖中沒有邊。

現在要進行 m 個操作,操作共有三種:

C a b,在點 a 和點 b 之間連一條邊,a 和 b 可能相等;

Q1 a b,詢問點 a 和點 b 是否在同一個連通塊中,a 和 b 可能相等;

Q2 a,詢問點 a 所在連通塊中點的數量;

輸入格式

第一行輸入整數 n 和 m。

接下來 m 行,每行包含一個操作指令,指令為 C a b,Q1 a b 或 Q2 a 中的一種。

輸出格式

對于每個詢問指令 Q1 a b,如果 a 和 b 在同一個連通塊中,則輸出 Yes,否則輸出 No。

對于每個詢問指令 Q2 a,輸出一個整數表示點 a 所在連通塊中點的數量

每個結果占一行。

數據范圍

1≤n,m≤105

輸入樣例:

5 5

C 1 2

Q1 1 2

Q2 1

C 2 5

Q2 5

輸出樣例:

Yes

2

3

#include<bits/stdc++.h> using namespace std; const int N=100010; int n,m; int p[N],sz[N];int findP(int x) {if(p[x]!=x) p[x]=findP(p[x]);return p[x]; }int main() {for(int i=0;i<N;i++){p[i]=i;sz[i]=1;}cin>>n>>m;while(m--){string op;cin>>op;int a,b;if(op=="C"){cin>>a>>b;if(a!=b){if(findP(a)!=findP(b)){sz[findP(b)]+=sz[findP(a)];}p[findP(a)]=findP(b);}}else if(op=="Q1"){cin>>a>>b;if(findP(a)==findP(b)) cout<<"Yes"<<endl;else cout<<"No"<<endl;}else{cin>>a;cout<<sz[findP(a)]<<endl;}}return 0; }

2.9.3 240. 食物鏈

動物王國中有三類動物 A,B,C,這三類動物的食物鏈構成了有趣的環形。

A 吃 B,B 吃 C,C 吃 A。

現有 N 個動物,以 1~N 編號。

每個動物都是 A,B,C 中的一種,但是我們并不知道它到底是哪一種。

有人用兩種說法對這 N 個動物所構成的食物鏈關系進行描述:

第一種說法是 1 X Y,表示 X 和 Y 是同類。

第二種說法是 2 X Y,表示 X 吃 Y。

此人對 N 個動物,用上述兩種說法,一句接一句地說出 K 句話,這 K 句話有的是真的,有的是假的。

當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。

當前的話與前面的某些真的話沖突,就是假話;

當前的話中 X 或 Y 比 N 大,就是假話;

當前的話表示 X 吃 X,就是假話。

你的任務是根據給定的 N 和 K 句話,輸出假話的總數。

輸入格式

第一行是兩個整數 N 和 K,以一個空格分隔。

以下 K 行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中 D 表示說法的種類。

若 D=1,則表示 X 和 Y 是同類。

若 D=2,則表示 X 吃 Y。

輸出格式

只有一個整數,表示假話的數目。

數據范圍

1≤N≤50000,

0≤K≤100000

輸入樣例:

100 7

1 101 1

2 1 2

2 2 3

2 3 3

1 1 3

2 3 1

1 5 5

輸出樣例:

3

#include<bits/stdc++.h> using namespace std; const int N=50010; int n,k; int p[N],d[N];int findP(int x) {if(p[x]!=x){int t=findP(p[x]);d[x]+=d[p[x]];p[x]=t;}return p[x]; } int main() {for(int i=0;i<N;i++){p[i]=i;}cin>>n>>k;int ans=0;for(int i=1;i<=k;i++){int t,x,y;cin>>t>>x>>y;if(x>n||y>n) ans++;else{int px=findP(x),py=findP(y);if(t==1){if(px==py){if((d[x]-d[y])%3) ans++;}else{p[px]=py;d[px]=d[y]-d[x];}}else{if(px==py){if((d[x]-d[y]-1)%3) ans++;}else{p[px]=py;d[px]=d[y]-d[x]+1;}}}}cout<<ans;return 0; }

2.10堆

堆 —— 模板題 AcWing 838. 堆排序, AcWing 839. 模擬堆

// h[N]存儲堆中的值, h[1]是堆頂,x的左兒子是2x, 右兒子是2x + 1 // ph[k]存儲第k個插入的點在堆中的位置 // hp[k]存儲堆中下標是k的點是第幾個插入的 int h[N], ph[N], hp[N], size;// 交換兩個點,及其映射關系 void heap_swap(int a, int b) {swap(ph[hp[a]],ph[hp[b]]);swap(hp[a], hp[b]);swap(h[a], h[b]); }void down(int u) {int t = u;if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;if (u != t){heap_swap(u, t);down(t);} }void up(int u) {while (u / 2 && h[u] < h[u / 2]){heap_swap(u, u / 2);u >>= 1;} }// O(n)建堆 for (int i = n / 2; i; i -- ) down(i);

2.10.1 838. 堆排序

輸入一個長度為 n 的整數數列,從小到大輸出前 m 小的數。

輸入格式

第一行包含整數 n 和 m。

第二行包含 n 個整數,表示整數數列。

輸出格式

共一行,包含 m 個整數,表示整數數列中前 m 小的數。

數據范圍

1≤m≤n≤105,

1≤數列中元素≤109

輸入樣例:

5 3

4 5 1 3 2

輸出樣例:

1 2 3

#include<bits/stdc++.h> using namespace std; const int N=100010; int n,m; priority_queue<int,vector<int>,greater<int> > q; int main() {cin>>n>>m;for(int i=0;i<n;i++){int x;cin>>x;q.push(x);}while(m--){cout<<q.top()<<" ";q.pop();} }

2.10.2 839. 模擬堆

維護一個集合,初始時集合為空,支持如下幾種操作:

I x,插入一個數 x;

PM,輸出當前集合中的最小值;

DM,刪除當前集合中的最小值(數據保證此時的最小值唯一);

D k,刪除第 k 個插入的數;

C k x,修改第 k 個插入的數,將其變為 x;

現在要進行 N 次操作,對于所有第 2 個操作,輸出當前集合的最小值。

輸入格式

第一行包含整數 N。

接下來 N 行,每行包含一個操作指令,操作指令為 I x,PM,DM,D k 或 C k x 中的一種。

輸出格式

對于每個輸出指令 PM,輸出一個結果,表示當前集合中的最小值。

每個結果占一行。

數據范圍

1≤N≤105

?109≤x≤109

數據保證合法。

輸入樣例:

8

I -10

PM

I -10

D 1

C 2 8

I 6

PM

DM

輸出樣例:

-10

6

#include<bits/stdc++.h> using namespace std; const int N=100010; int n; int h[N],sz=0; int ph[N],hp[N];void heap_swap(int a,int b) {swap(ph[hp[a]],ph[hp[b]]);swap(hp[a],hp[b]);swap(h[a],h[b]); } void down(int u) {int t=u;if(2*u<=sz&&h[2*u]<h[t]) t=2*u;if(2*u+1<=sz&&h[2*u+1]<h[t]) t=2*u+1;if(u!=t){heap_swap(u,t);down(t);} }void up(int u) {while(u/2>0&&h[u]<h[u/2]){heap_swap(u,u/2);u/=2;} } int main() {int id=0;cin>>n;while(n--){string op;int k,x;cin>>op;if(op=="I"){cin>>x;h[++sz]=x;id++;ph[id]=sz,hp[sz]=id;up(sz);}else if(op=="PM") cout<<h[1]<<endl;else if(op=="DM"){heap_swap(1,sz);sz--;down(1);}else if(op=="D"){cin>>k;k=ph[k];heap_swap(k,sz);sz--;down(k),up(k);}else if(op=="C"){cin>>k>>x;k=ph[k];h[k]=x;down(k),up(k);}}return 0; }

2.11哈希表

一般哈希 —— 模板題 AcWing 840. 模擬散列表

(1) 拉鏈法

int h[N], e[N], ne[N], idx;// 向哈希表中插入一個數void insert(int x){int k = (x % N + N) % N;e[idx] = x;ne[idx] = h[k];h[k] = idx ++ ;}// 在哈希表中查詢某個數是否存在bool find(int x){int k = (x % N + N) % N;for (int i = h[k]; i != -1; i = ne[i])if (e[i] == x)return true;return false;}

(2) 開放尋址法

int h[N];// 如果x在哈希表中,返回x的下標;如果x不在哈希表中,返回x應該插入的位置int find(int x){int t = (x % N + N) % N;while (h[t] != null && h[t] != x){t ++ ;if (t == N) t = 0;}return t;}

字符串哈希 —— 模板題 AcWing 841. 字符串哈希

核心思想:將字符串看成P進制數,P的經驗值是131或13331,取這兩個值的沖突概率低

小技巧:取模的數用2^64,這樣直接用unsigned long long存儲,溢出的結果就是取模的結果

typedef unsigned long long ULL; ULL h[N], p[N]; // h[k]存儲字符串前k個字母的哈希值, p[k]存儲 P^k mod 2^64// 初始化 p[0] = 1; for (int i = 1; i <= n; i ++ ) {h[i] = h[i - 1] * P + str[i];p[i] = p[i - 1] * P; }// 計算子串 str[l ~ r] 的哈希值 ULL get(int l, int r) {return h[r] - h[l - 1] * p[r - l + 1]; }

2.11.1 840. 模擬散列表

維護一個集合,支持如下幾種操作:

I x,插入一個數 x;

Q x,詢問數 x 是否在集合中出現過;

現在要進行 N 次操作,對于每個詢問操作輸出對應的結果。

輸入格式

第一行包含整數 N,表示操作數量。

接下來 N 行,每行包含一個操作指令,操作指令為 I x,Q x 中的一種。

輸出格式

對于每個詢問指令 Q x,輸出一個詢問結果,如果 x 在集合中出現過,則輸出 Yes,否則輸出 No。

每個結果占一行。

數據范圍

1≤N≤105

?109≤x≤109

輸入樣例:

5

I 1

I 2

I 3

Q 2

Q 5

輸出樣例:

Yes

No

#include<bits/stdc++.h> using namespace std; map<int,bool> mp; int n; int main() {cin>>n;while(n--){char op;int x;cin>>op>>x;if(op=='I') mp[x]=true;else{if(mp.count(x)) cout<<"Yes"<<endl;else cout<<"No"<<endl;}}return 0; }

2.11.2 841. 字符串哈希

給定一個長度為 n 的字符串,再給定 m 個詢問,每個詢問包含四個整數 l1,r1,l2,r2,請你判斷 [l1,r1] 和 [l2,r2] 這兩個區間所包含的字符串子串是否完全相同。

字符串中只包含大小寫英文字母和數字。

輸入格式

第一行包含整數 n 和 m,表示字符串長度和詢問次數。

第二行包含一個長度為 n 的字符串,字符串中只包含大小寫英文字母和數字。

接下來 m 行,每行包含四個整數 l1,r1,l2,r2,表示一次詢問所涉及的兩個區間。

注意,字符串的位置從 1 開始編號。

輸出格式

對于每個詢問輸出一個結果,如果兩個字符串子串完全相同則輸出 Yes,否則輸出 No。

每個結果占一行。

數據范圍

1≤n,m≤105

輸入樣例:

8 3

aabbaabb

1 3 5 7

1 3 6 8

1 2 1 2

輸出樣例:

Yes

No

Yes

#include<bits/stdc++.h> using namespace std; typedef unsigned long long ULL; const int N=100010,P=131; string str; int n,m; ULL h[N],p[N];ULL hashstr(int l,int r) {return h[r]-h[l-1]*p[r-l+1]; } int main() {cin>>n>>m;cin>>str;p[0]=1;for(int i=1;i<=n;i++){h[i]=h[i-1]*P+str[i-1];p[i]=p[i-1]*P;}while(m--){int l,r,ll,rr;cin>>l>>r>>ll>>rr;if(hashstr(l,r)==hashstr(ll,rr)) cout<<"Yes"<<endl;else cout<<"No"<<endl;}return 0; }

2.12 STL

C++ STL簡介

vector, 變長數組,倍增的思想size() 返回元素個數empty() 返回是否為空clear() 清空front()/back()push_back()/pop_back()begin()/end()[]支持比較運算,按字典序pair<int, int>first, 第一個元素second, 第二個元素支持比較運算,以first為第一關鍵字,以second為第二關鍵字(字典序)string,字符串size()/length() 返回字符串長度empty()clear()substr(起始下標,(子串長度)) 返回子串c_str() 返回字符串所在字符數組的起始地址queue, 隊列size()empty()push() 向隊尾插入一個元素front() 返回隊頭元素back() 返回隊尾元素pop() 彈出隊頭元素priority_queue, 優先隊列,默認是大根堆size()empty()push() 插入一個元素top() 返回堆頂元素pop() 彈出堆頂元素定義成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;stack,size()empty()push() 向棧頂插入一個元素top() 返回棧頂元素pop() 彈出棧頂元素deque, 雙端隊列size()empty()clear()front()/back()push_back()/pop_back()push_front()/pop_front()begin()/end()[]set, map, multiset, multimap, 基于平衡二叉樹(紅黑樹),動態維護有序序列size()empty()clear()begin()/end()++, -- 返回前驅和后繼,時間復雜度 O(logn)set/multisetinsert() 插入一個數find() 查找一個數count() 返回某一個數的個數erase()(1) 輸入是一個數x,刪除所有x O(k + logn)(2) 輸入一個迭代器,刪除這個迭代器lower_bound()/upper_bound()lower_bound(x) 返回大于等于x的最小的數的迭代器upper_bound(x) 返回大于x的最小的數的迭代器map/multimapinsert() 插入的數是一個pairerase() 輸入的參數是pair或者迭代器find()[] 注意multimap不支持此操作。 時間復雜度是 O(logn)lower_bound()/upper_bound()unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表和上面類似,增刪改查的時間復雜度是 O(1)不支持 lower_bound()/upper_bound(), 迭代器的++--bitset, 圧位bitset<10000> s;~, &, |, ^>>, <<==, !=[]count() 返回有多少個1any() 判斷是否至少有一個1none() 判斷是否全為0set() 把所有位置成1set(k, v) 將第k位變成vreset() 把所有位變成0flip() 等價于~flip(k) 把第k位取反

總結

以上是生活随笔為你收集整理的【第二讲】数据结构的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。