【第二讲】数据结构
來自:算法基礎課
文章目錄
- 第二講 數據結構
- 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位取反總結
- 上一篇: 生成对抗性网络简介
- 下一篇: kvm虚拟机grub失败