基本数据结构篇(三万字总结)
數(shù)據(jù)結(jié)構(gòu)
- 棧
- 編輯器(對(duì)頂棧)
- 火車進(jìn)棧
- 火車進(jìn)棧問題(卡特蘭數(shù))
- 大數(shù)相乘
- 分解質(zhì)因數(shù)
- 階乘分解質(zhì)因數(shù)
- 壓位
- 配套的高精度除法
- 隊(duì)列
- 小組隊(duì)列
- 蚯蚓
- 雙端隊(duì)列
- 最大子序和(單調(diào)隊(duì)列)
- 哈希
- 雪花雪花雪花(序列的最小表示)
- 兔子與兔子
- 回文子串的最大長(zhǎng)度
- KMP
- 周期(next 數(shù)組的性質(zhì))
- Trie
- 前綴統(tǒng)計(jì)
- 最大異或?qū)?/li>
- 最長(zhǎng)異或值路徑
- 二叉堆
- 序列
- 數(shù)據(jù)備份
- 合并果子 (二叉哈夫曼樹)
- 荷馬史詩(k叉哈夫曼樹)
- 小結(jié)
- 小知識(shí)點(diǎn)
- 解題思路
- 由數(shù)據(jù)范圍反推算法復(fù)雜度以及算法內(nèi)容
棧
編輯器(對(duì)頂棧)
原題鏈接
解題思路:
解題思路鏈接:https://www.acwing.com/solution/content/27188/
代碼:
#include <bits/stdc++.h> using namespace std; const int N=1e6+100; int t,x,sum[N],f[N],now; stack<int> a,b,c; int main() {while(scanf("%d\n",&t)!=EOF)//之前在HDU提交,所以是多組數(shù)據(jù){a=c;//STL特性,這里就是清空操作b=c;f[0]=-1e7;//初始化sum[0]=0;for(int i=1;i<=t;i++){char ch=getchar();//讀入if (ch=='I')//插入操作{scanf(" %d",&x);a.push(x);//將a插入棧中sum[a.size()]=sum[a.size()-1]+a.top();//前1~a.size()-1的前綴和,加上這個(gè)一個(gè)新來的,構(gòu)成1~a.size()f[a.size()]=max(f[a.size()-1],sum[a.size()]);//看是之前的最大值大,還是新來的最大值大}if (ch=='D')if (!a.empty())//只要棧不為空,就刪除a.pop();if (ch=='L')//左傾思想(博古+文化大革命)(手動(dòng)滑稽)if(!a.empty())//只要不為空b.push(a.top()),a.pop();//a+b等于整個(gè)插入序列,b負(fù)責(zé)管理當(dāng)前光標(biāo)右邊的序列.if (ch=='R')//右傾思想(陳獨(dú)秀)(手動(dòng)滑稽){if (!b.empty())//b不為空{a.push(b.top());//a負(fù)責(zé)管理1~當(dāng)前光標(biāo).所以現(xiàn)在a往右了,那么必然是要加入b棧的開頭,因?yàn)閎棧管理當(dāng)前光標(biāo)的右邊.b.pop();sum[a.size()]=sum[a.size()-1]+a.top();//同樣的還是重新定義.f[a.size()]=max(f[a.size()-1],sum[a.size()]);//見插入操作.}}if (ch=='Q'){scanf(" %d",&x);printf("%d\n",f[x]);//輸出當(dāng)前最大值區(qū)間.}getchar();//換行符讀入}}return 0; }代碼鏈接:https://www.acwing.com/solution/content/1275/火車進(jìn)棧
題目鏈接
解題思路:
如下圖,我們?cè)赿fs()的時(shí)候要維護(hù)三個(gè)狀態(tài),狀態(tài)一就是出棧的序列,狀態(tài)二是還在棧里的元素,狀態(tài)三是還沒有進(jìn)棧的元素。我們?cè)诿恳粋€(gè)狀態(tài)下都有兩個(gè)操作可以進(jìn)行,第一個(gè)操作是將元素進(jìn)棧,第二個(gè)操作是元素出棧。邊界條件是state1 的元素個(gè)數(shù)為n。題目中要我們按字典序的輸出方案,因此我們考慮以下操作一與操作二的順序。因?yàn)閟tate3中的元素肯定是比state2中的元素還要大的,因此我們先執(zhí)行操作二再執(zhí)行操作一即可。
代碼:
#include<stdio.h> #include<stack> #include<vector>using namespace std;int n,cnt = 20; vector<int> state1; stack<int>state2; int state3 = 1;void dfs(){if(!cnt)return ;if(state1.size() == n){cnt --;for(auto x : state1) printf("%d",x);putchar('\n');return ;}// state2 要滿足 棧不為空if(!state2.empty()){state1.push_back(state2.top());state2.pop();dfs();state2.push(state1.back());state1.pop_back();}// state3 要滿足的是元素個(gè)數(shù)不能大于nif(state3 <= n){state2.push(state3);state3 ++ ;dfs();state3 -- ;state2.pop();} }int main(){scanf("%d",&n);dfs();return 0; }火車進(jìn)棧問題(卡特蘭數(shù))
原題鏈接
解題思路:
本題是一個(gè)卡特蘭數(shù),相關(guān)解釋可以百度。卡特蘭數(shù)的本質(zhì)是:任何前綴某一類的元素大于等于另一類,如果題目符合這個(gè)性質(zhì),那么大概率就是卡特蘭數(shù)了。
首先我們要求高精,(n2n)n+1\frac{\binom{n}{2n}}{n+1}n+1(2nn?)? 也就是 2n!n!?n!?(n+1)\frac{2n!}{n! * n! * (n+1)}n!?n!?(n+1)2n!? 最快的辦法是求出這玩意所有的質(zhì)因數(shù)及其次數(shù),然后求所有數(shù)乘積即可。同時(shí)在高精度乘法的時(shí)候壓位來提高速度。
解題步驟:
- 利用素?cái)?shù)篩法,求出 2~2n 內(nèi)的所有素?cái)?shù)。
- 求出 2n! 與 n! 中質(zhì)因子的個(gè)數(shù)。
- 減去 n + 1 中質(zhì)因子個(gè)數(shù)。
- 大數(shù)乘法。
知識(shí)點(diǎn):
大數(shù)相乘
分解質(zhì)因數(shù)
階乘分解質(zhì)因數(shù)
壓位
代碼:
#include<stdio.h> #include<vector>using namespace std;const int N = 1200010; int primes[N],cnt; bool st[N]; int power[N]; //素?cái)?shù)篩法 void get_primes(int n ){cnt = 0;for(int i = 2 ;i <= n ;++i)if(!st[i]){primes[cnt++] = i;for(int j = 2 * i ; j <= n ; j += i)st[j] = true;} } // 得到 n! 中 p 因子的個(gè)數(shù) int get(int n , int p ){int s = 0;while(n){s += n / p;n /= p ;}return s; }void mutil(vector<int>&a,int b){int t = 0; // 進(jìn)位int n = a.size();for(int i = 0 ; i < n ; ++i){a[i] = a[i] * b + t;t = a[i] / 10000;a[i] %= 10000;}while(t){a.push_back( t % 10000);t /= 10000;} }void out(vector<int> a){printf("%d",a.back());for(int i = a.size() - 2 ; i >= 0 ; -- i)printf("%04d",a[i]);putchar('\n'); }int main(){int n;scanf("%d",&n);get_primes(2 * n);for(int i = 0 ; i < cnt ; ++i){int p = primes[i];// 對(duì)于計(jì)算·每一個(gè)質(zhì)因子 在 n !中的個(gè)數(shù)power[p] = get(2*n,p) - get(n,p) * 2;}// 對(duì)分母的 n + 1 進(jìn)行質(zhì)因數(shù)分解int k = n + 1 ;for(int i = 0 ; i < cnt && primes[i] <= k ; ++i ){int p = primes[i], s = 0;while(k % p == 0){k /= p;s++;}power[p] -= s;}vector<int>ans;ans.push_back(1);// 對(duì) 還有的質(zhì)因數(shù)進(jìn)行乘法運(yùn)算for(int i = 0 ; i < cnt ; ++i){int p = primes[i];for(int j = 0; j < power[p] ; ++j) // 質(zhì)因數(shù)個(gè)數(shù)mutil(ans,p);}out(ans);return 0; }配套的高精度除法
void div(vector<int>&a,int b){int t = 0; // 上一位的余數(shù)·for(int i = a.size()-1 ; i >= 0 ;++i){a[i] += t * 10; t = a[i] % b;a[i] /= b;}// 把高位的0除去while(a.size() > 1 && a.back() == 0)a.pop_back(); }隊(duì)列
小組隊(duì)列
題目鏈接
解題思路:
因?yàn)殛?duì)伍中只要前邊有自己隊(duì)員就不用在隊(duì)尾排隊(duì),所以如果我們用一個(gè)對(duì)列來實(shí)現(xiàn)的話不好弄。因?yàn)椴迦腙?duì)員后也是一個(gè)隊(duì)列。因?yàn)槲覀儠?huì)每一個(gè)小組建立一個(gè)隊(duì)列。而排隊(duì)的隊(duì)列我們存的是組號(hào)。當(dāng)我們要出隊(duì)時(shí),我么就查詢對(duì)首值得到其對(duì)應(yīng)的組號(hào),依據(jù)這個(gè)組號(hào)去改組號(hào)對(duì)應(yīng)的隊(duì)列里邊進(jìn)行出隊(duì)。
- 進(jìn)隊(duì)操作:當(dāng)一個(gè)編號(hào)為x的隊(duì)員進(jìn)隊(duì)時(shí),我們得到其對(duì)應(yīng)的組號(hào)。然后壓入該組號(hào)對(duì)應(yīng)的隊(duì)列里邊,如果壓入之前隊(duì)列為空,那么將該組號(hào)壓入 q0 的隊(duì)尾。
- 出隊(duì)操作:我們讀取q0 中對(duì)首元素對(duì)應(yīng)的組號(hào),將這個(gè)組號(hào)的隊(duì)列的對(duì)首元素出隊(duì)。如果在出隊(duì)后隊(duì)列為空,那么就將q0的對(duì)首元素出隊(duì),表示這個(gè)組的成員已經(jīng)全部出隊(duì)了。
代碼:
#include<stdio.h> #include<string> #include<queue> #include<map> #include<string> #include<iostream> using namespace std;int t,cnt ;int main(){while(~scanf("%d",&t)){if(!t)break;cnt ++;map<int,int>vis;queue<int> q[1010];for(int i = 1 , x ,n;i <= t ; ++i){scanf("%d",&n);while(n -- ){scanf("%d",&x);vis[x] = i; // 對(duì)于同一組的打上標(biāo)簽}}printf("Scenario #%d\n",cnt);string s;while(cin>>s){if( s == "STOP")break;if(s == "ENQUEUE"){int x,pos;scanf("%d",&x);pos = vis[x]; // 找到編號(hào)對(duì)應(yīng)得組if(q[pos].empty())q[0].push(pos);q[pos].push(x);}if(s == "DEQUEUE"){int pos = q[0].front();printf("%d\n",q[pos].front());q[pos].pop();if(q[pos].empty())q[0].pop();}}puts("");} return 0; }蚯蚓
題目鏈接
解題思路:
我們發(fā)現(xiàn)如果蚯蚓i被切斷后,然后蚯蚓j也被切斷,那么我們發(fā)現(xiàn),i蚯蚓的切后兩段的長(zhǎng)度,都會(huì)大于j蚯蚓的切后的兩段的長(zhǎng)度,因此這里有單調(diào)遞減的性質(zhì).
找到了性質(zhì),那么我們完全可以通過三個(gè)隊(duì)列模擬優(yōu)先隊(duì)列,一個(gè)隊(duì)列維護(hù)切后的第一段,一個(gè)隊(duì)列維護(hù)切后的第二段,另外一個(gè)隊(duì)列,里面存儲(chǔ)蚯蚓長(zhǎng)度,記住長(zhǎng)度是從高到低,排好序的長(zhǎng)度,那么每一次將被切斷的蚯蚓,肯定是這三個(gè)隊(duì)列的隊(duì)頭,因?yàn)槲覀冞@道題目具有單調(diào)遞減的性質(zhì),所以其實(shí)這道題目三個(gè)隊(duì)列,都隱藏著單調(diào)隊(duì)列的性質(zhì).
解題步驟
第一步將原始的長(zhǎng)度從大到小排個(gè)序。之后進(jìn)行m次操作。每一次取出三個(gè)隊(duì)列對(duì)首的最大值x,x在加上偏移量delta后,分成兩段后,偏移量加上q后,分別減去偏移量后放進(jìn)隊(duì)列中。
技巧:
- 在本題中有一個(gè)對(duì)集合進(jìn)行整體加上一個(gè)數(shù)的操作。0(n)的思路是把每一個(gè)元素都加上。不過有一個(gè)O(1)操作是,設(shè)置一個(gè)偏移量delta,存在集合中的數(shù)據(jù)是一個(gè)相對(duì)值,在取出后加上偏移量就得到真實(shí)值。然后在放入集合的時(shí)候減去偏移量即可。
代碼:
#include<stdio.h> #include<algorithm> #include<limits.h>using namespace std; const int N = 7000010; typedef long long ll; ll q1[N] , q2[N] , q3[N]; int h1,h2,h3,t1,t2 = -1,t3 = -1; int n,m,q,u,v,t; ll delta;ll get_max(){ll maxx = INT_MIN;if(h1 <= t1) maxx = max(maxx,q1[h1]);if(h2 <= t2) maxx = max(maxx,q2[h2]);if(h3 <= t3) maxx = max(maxx,q3[h3]);if(h1 <= t1 && q1[h1] == maxx) h1++;else if(h2 <= t2 && q2[h2] == maxx) h2++;else h3++;return maxx; }int main(){scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);for(int i = 0 ;i < n ; ++i)scanf("%d",&q1[i]);sort(q1,q1+n);reverse(q1,q1+n);t1 = n - 1;for(int i = 1 ; i <= m ; ++i){ll x = get_max();x += delta;if(i % t == 0)printf("%d ",x);int left = x * 1ll * u /v;int right = x - left;delta += q;q2[++t2] = left - delta;q3[++t3] = right - delta;}puts("");for(int i = 1 ; i <= n + m ; ++i){ll x = get_max();if(i % t == 0)printf("%d ",x + delta);}puts("");return 0; }雙端隊(duì)列
題目鏈接
題解:
以下的大佬總結(jié)得比我好,所以就貼上了
題解出處
收獲:
- 找出元素相同的區(qū)間,while(j < n && a[i].first == a[j].first) j++;
- 貪心來維護(hù)單調(diào)性,同時(shí)設(shè)置一個(gè)變量來保存當(dāng)前的單調(diào)性。
代碼:
#include<stdio.h> #include<algorithm> #include<limits.h>using namespace std;typedef pair<int,int>PII;const int N = 200010;PII a[N]; int n;int main(){scanf("%d",&n);for(int i = 0 ; i < n ;++i){scanf("%d",&a[i].first);a[i].second = i;}sort(a,a+n);int res = 1 , last = INT_MAX ;int dir = -1 ;for(int i = 0 ; i < n ; ){int j = i , minp , maxp;while(j < n && a[i].first == a[j].first) j++;minp = a[i].second , maxp = a[j - 1].second;if(dir == -1 ){if(last > maxp) last = minp; // 在接的時(shí)候是反過來接的,這樣才可以讓接的這一段單調(diào)減else dir = 1 , last = maxp;}else{if(last < minp) last = maxp;//同理else res++,last = minp , dir = -1;// 不能接上的話,新建立的是遞減的所以要反著接}i = j;}printf("%d\n",res);return 0; }最大子序和(單調(diào)隊(duì)列)
題目鏈接
解題思路:
簡(jiǎn)單來說,我們先利用前綴和數(shù)組,將題目轉(zhuǎn)變?yōu)閿?shù)組中兩個(gè)元素位置相距不大于m的情況下使得后者減去前者得到的值最大。那么我們就轉(zhuǎn)變?yōu)槊勘闅v一個(gè)右端點(diǎn)的時(shí)候,找到前m個(gè)元素的最小值。這樣就是找到了以該右端點(diǎn)的最大值。如果平常思路是遍歷前m個(gè)元素找到最小值。不過我們通過單調(diào)遞增隊(duì)列維護(hù)一個(gè)不超過m個(gè)元素的隊(duì)列。該隊(duì)列的對(duì)首的小標(biāo)就是以i為右端點(diǎn)前m個(gè)元素中最小值所在的下標(biāo)。
代碼:
#include<stdio.h> #include<limits.h> #include<algorithm>using namespace std;typedef long long LL;const int N = 300010;LL sum[N]; int q[N]; int n , m ;int main(){scanf("%d%d",&n,&m);for(int i = 1 ; i <= n ;++i){scanf("%lld",&sum[i]);sum[i] += sum[i-1] ;}int hh = 0 ,tt = 0; LL res = INT_MIN;for(int i = 1 ; i <= n ; ++i){if(hh <= tt && q[hh] < i - m) hh++; // i - m 得到第 m + 1 個(gè)元素的位置res = max(res,sum[i] - sum[q[hh]]);// 因?yàn)橐S護(hù)一個(gè)單調(diào)遞增隊(duì)列。while(hh <= tt && sum[q[tt]] >= sum[i]) tt--; // 因?yàn)檫@里求值就是利用前綴和來的,可以把前綴和看成一個(gè)元素就好q[++tt] = i;}printf("%lld\n",res);return 0; }哈希
雪花雪花雪花(序列的最小表示)
題目鏈接
本題大致題意:
給了n和長(zhǎng)度為6的序列,判斷兩個(gè)序列是否相識(shí)。可以進(jìn)行的操作是旋轉(zhuǎn):即將序列的第一個(gè)放到最后一個(gè)。另一個(gè)操作是翻轉(zhuǎn)。
正確解法應(yīng)該是用哈希的,不過可以用別的方法解決。本文介紹另一種解法。哈希解法
解題思路:
先介紹以下 序列的最小表示
長(zhǎng)度為n的序列的最小表示:一個(gè)元素為n的序列旋轉(zhuǎn)n次為得到原序列。在旋轉(zhuǎn)過程中產(chǎn)生的序列中,字典序最小的序列就是該序列的最小表示。
因此,如果本題的操作只有旋轉(zhuǎn),我們依次求出每一個(gè)序列的最小表示。比較是否有相同的,有則說明有相似的序列。不過本題還有一個(gè)翻轉(zhuǎn)操作,那么我們同時(shí)還求出序列翻轉(zhuǎn)之后的最小序列。在這兩個(gè)序列中找最小的,比較其是否相同。
技巧:
代碼:
#include<iostream> #include<cstring> #include<algorithm> #include<stdio.h> using namespace std;const int N = 1E5 + 10;int snows[N][6],idx[N]; int n ;bool cmp_array(int a[] , int b[]){for(int i = 0 ;i < 6 ; ++i )if(a[i] > b[i])return false;else if( a[i] < b[i])return true;return false; }bool cmp(int a , int b){return cmp_array(snows[a],snows[b]); }// 求長(zhǎng)度為6 的序列的最小表示 void get_min(int a[]){static int b[12];// 復(fù)制for(int i = 0 ; i < 12 ; ++i)b[i] = a[i % 6];int i = 0 , j = 1 , k;while(i < 6 && j < 6){for(k = 0 ; k < 6 && b[i+k] == b[j+k] ; ++k);if(k == 6)break;if(b[i+k] > b[j+k]){i += k + 1;if(i == j ) ++i;}else{j += k + 1;if(i == j) ++j;}}k = min(i,j);for(int i = 0 ; i < 6 ; ++i)a[i] = b[i+k]; }int main(){scanf("%d",&n);int snow[6],isnow[6];for(int i = 0 ; i < n ; ++ i ){for(int j = 0 , k = 5 ; j < 6 ; ++j , --k){scanf("%d",&snow[j]);isnow[k] = snow[j];}get_min(snow);get_min(isnow);if(cmp_array(snow,isnow)) memcpy(snows[i],snow , sizeof snow);else memcpy(snows[i] , isnow , sizeof isnow);idx[i] = i;}sort(idx,idx + n , cmp);bool flag = false;for(int i = 1 ; i < n ; ++i)if(!cmp(idx[i],idx[i-1]) && ! cmp(idx[i-1],idx[i])){flag = true;break;}if(flag)puts("Twin snowflakes found.");else puts("No two snowflakes are alike.");return 0; }兔子與兔子
題目鏈接
解題思路:
這題沒啥好說的了,就是一個(gè)字符串哈希的模板題。
代碼:
#include<stdio.h> #include<cstring>using namespace std;typedef unsigned long long ULL; const int N = 1E6 + 10;char s[N]; ULL p[N],h[N]; // 記得利用unsigned long longint m;int main(){scanf("%s",s+1);scanf("%d",&m);int len = strlen(s+1);p[0] = 1; // 131^0for(int i = 1 ; i <= len ; ++i){h[i] = h[i-1] * 131 + (s[i] - 'a' + 1);//hash 1~ip[i] = p[i-1] * 131;//131^i}int l1 , r1 ,l2,r2;for(int i = 1 ; i <= m ; ++i){scanf("%d%d%d%d",&l1,&r1,&l2,&r2);int t1 = r1 - l1 + 1 , t2 = r2 - l2 + 1;if((h[r1] - h[l1 - 1] * p[t1]) == (h[r2] - h[l2-1] * p[t2]))puts("Yes");elseputs("No");}return 0; }回文子串的最大長(zhǎng)度
題目鏈接
題解:
來自:https://www.acwing.com/solution/content/30482/
收獲:
注意:本題二分,是找 小于等于一個(gè)半徑長(zhǎng)度r的最大的一個(gè)(即r或r的前驅(qū))所以使用的二分方法是:
while(l < r ){int mid = (l + r + 1 ) / 2;if(get(hl,i - mid , i - 1) != get(hr,n - (i + mid)+1,n - (i + 1) +1)) r = mid - 1;else l = mid ;}代碼:
#include<stdio.h> #include<algorithm> #include<cstring>using namespace std;typedef unsigned long long ULL; const int N = 2 * 1E7 + 10 ,base = 131; char str[N]; ULL hl[N],hr[N],p[N];ULL get(ULL h[] , int l ,int r ){return h[r] - h[l-1] * p[r - l + 1]; }int main(){int T = 1 ;while(scanf("%s",str+1),strcmp(str+1,"END")){int n = strlen(str+1);for(int i = 2 * n ; i > 0 ; i -= 2 ){str[i] = str[i/2];str[i-1] = 'z' + 1;}n *= 2;p[0] = 1;for(int i = 1 ,j = n; i <= n ; ++ i,j--){hl[i] = hl[i-1] * base + str[i] - 'a' + 1;hr[i] = hr[i-1] * base + str[j] - 'a' + 1;p[i] = p[i-1] * base;}int res = 0;for(int i = 1 ; i <= n ; ++i){int l = 0 , r = min(i-1,n-i);while(l < r ){int mid = (l + r + 1 ) / 2;if(get(hl,i - mid , i - 1) != get(hr,n - (i + mid)+1,n - (i + 1) +1)) r = mid - 1;else l = mid ;}if(str[i - l] <= 'z') res = max(res,l+1);else res = max(res,l);}printf("Case %d: %d\n",T++,res);}return 0; }KMP
周期(next 數(shù)組的性質(zhì))
題目鏈接
題解出處:https://www.acwing.com/solution/content/28244/
代碼:
#include<stdio.h>const int N = 1E6 + 10; char str[N]; int Next[N]; int n ; // 求next數(shù)組模板。 void get_next(){Next[1] = 0;for(int i = 2 , j = 0; i <= n ; ++i){while(j && str[i] != str[j+1])j = Next[j];if(str[i] == str[j+1]) j++;Next[i] = j ;} }// 對(duì)于KMP 一般下標(biāo)從1開始 int main(){int T = 1 ;while(~scanf("%d",&n) && n){scanf("%s",str+1);get_next();printf("Test case #%d\n",T++);for(int i = 2 ; i <= n ; ++i){int t = i - Next[i];if(t != i && i % t == 0)printf("%d %d\n",i,i/t);}puts("");}return 0; }Trie
前綴統(tǒng)計(jì)
題目鏈接
題目大意:先給了n個(gè)字符串,之后給m個(gè)字符串,在這m個(gè)字符串中詢問其前綴出現(xiàn)在n個(gè)字符串的個(gè)數(shù)。暴力的做法就是對(duì)于每一個(gè)前綴依次與n個(gè)字符串進(jìn)行比較O(n2)的。而這里涉及到了字符串得匹配問題,而trie樹恰可以解決這個(gè)問題。我們利用前n個(gè)字符串建立一個(gè)Trie樹,之后對(duì)于m個(gè)字符串,依次將這個(gè)字符串放到Trie樹里邊,沿著其前綴走一篇,如果經(jīng)過的一個(gè)節(jié)點(diǎn)又結(jié)尾標(biāo)記,就加上1.
題解:
- 記錄結(jié)尾個(gè)數(shù)的方法就是,額外建立一個(gè)數(shù)組,將每一次插入后最后的指針p,對(duì)應(yīng)的位置加1.
對(duì)于查找字符串是否存在的話,
void insert(){int len = strlen(str);int p = 0;for(int i = 0 ; i < len ; ++i){int &s = trie[p][str[i] - 'a']; // 引用類型if(!s)s = ++tot;p = s;}end[p] = true; }int query(){int p = 0 ;int len = strlen(str);for(int i = 0 ; i < len ; ++i){int &s = trie[p][str[i] - 'a'];if(!s)return false;p = s;}return end[p]; }代碼:
#include<stdio.h> #include<cstring> const int N = 1E6 + 10 , M = 5E5 + 10;int n , m; char str[N]; int trie[M][26],end[M],tot;void insert(){int len = strlen(str);int p = 0;for(int i = 0 ; i < len ; ++i){int &s = trie[p][str[i] - 'a']; // 引用類型if(!s)s = ++tot;p = s;}end[p] ++; // 如果只是做一個(gè)結(jié)尾標(biāo)記的話可以用bool 數(shù)組,這里設(shè)置為true }int query(){int p = 0 , res = 0;int len = strlen(str);for(int i = 0 ; i < len ; ++i){int &s = trie[p][str[i] - 'a'];if(!s)break;p = s;res += end[p];}return res; }int main(){scanf("%d%d",&n,&m);for(int i = 0 ; i < n ; ++i){scanf("%s",str);insert();}for(int i = 0 ; i < m ; ++ i){scanf("%s",str);printf("%d\n",query());}return 0;}最大異或?qū)?/h2>
題目鏈接
解題思路;
(暴力枚舉) O(n2)
優(yōu)化算法:
(trie樹)
trie樹中要明確兩個(gè)問題:
son[N][x]是個(gè)啥?idx是個(gè)啥?
首先son[N][x]這是個(gè)二維數(shù)組。
第一維N是題目給的數(shù)據(jù)范圍,像在trie樹中的模板題當(dāng)中N為字符串的總長(zhǎng)度**(這里的總長(zhǎng)度為所有的字符串的長(zhǎng)度加起來)**,在本題中N需要自己計(jì)算,最大為N*31(其實(shí)根本達(dá)不到這么大,舉個(gè)簡(jiǎn)單的例子假設(shè)用0和1編碼,按照前面的計(jì)算最大的方法應(yīng)該是4乘2=8但其實(shí)只有6個(gè)結(jié)點(diǎn))。
第二維x代表著兒子結(jié)點(diǎn)的可能性有多少,模板題中是字符串,而題目本身又限定了均為小寫字母所以只有26種可能性,在本題中下一位只有0或者1兩種情況所以為2。
而這個(gè)二維數(shù)組本身存的是當(dāng)前結(jié)點(diǎn)的下標(biāo),就是N嘍,所以總結(jié)的話son[N][x]存的就是第N的結(jié)點(diǎn)的x兒子的下標(biāo)是多少,然后idx就是第一個(gè)可以用的下標(biāo)。
鏈接:https://www.acwing.com/solution/content/6156/
代碼:
#include<stdio.h> #include<algorithm>using namespace std;const int N = 1E5 + 10 , M = 5e6 + 10;int trie[M][2],tot; int a[N]; int n ;void insert(int x){int p = 0;for(int i = 30 ; ~i ; --i){int &s = trie[p][x >> i & 1];if(!s)s = ++tot; // 創(chuàng)建新節(jié)點(diǎn)p = s;} }int query(int x){int res = 0 , p = 0;for(int i = 30 ; ~i ; --i){int s = x>>i & 1;if(trie[p][!s]){res += 1<<i; // 將res二進(jìn)制第i位置為1p = trie[p][!s];}else{res += 0<<i;// 沒有用,可以不用p = trie[p][s];}}return res; }int main(){scanf("%d",&n);for(int i = 0 ; i < n ;++i){scanf("%d",&a[i]);insert(a[i]);}int res = 0;for(int i = 0 ; i < n ; ++i){res = max(res, query(a[i]));}printf("%d\n",res);return 0; }最長(zhǎng)異或值路徑
題目鏈接
解題思路:
難點(diǎn):本題在上題的基礎(chǔ)上,應(yīng)該是dfs是一個(gè)難點(diǎn)。因?yàn)榭赡苁且粋€(gè)多叉樹,所以我們通過建圖來dfs。同時(shí)因?yàn)槭且粋€(gè)無向圖,一個(gè)節(jié)點(diǎn)的出邊有一條是連向其父節(jié)點(diǎn)的,因此我們?yōu)榱吮苊膺@種情況在dfs的時(shí)候把父節(jié)點(diǎn)的信息帶過來。
代碼:
#include<stdio.h> #include<algorithm> #include<cstring> using namespace std;const int N = 1E5 + 10 ,M = 5e6 + 10;int head[N] , Next[N*2] , e[N*2],c[N*2],cnt; int trie[M][2],tot; int a[N]; int n ;//e 放的是邊的終點(diǎn),c是邊權(quán), void add(int u , int v ,int w){e[cnt] = v;c[cnt] = w ;Next[cnt] = head[u] ; head[u] = cnt++; }void dfs(int u,int father , int sum){a[u] = sum;// 遍歷父節(jié)點(diǎn)的所有出邊for(int i = head[u] ; ~i ; i = Next[i]){int j = e[i]; // 下一個(gè)節(jié)點(diǎn)// 不能返回,即不看是其父節(jié)點(diǎn)if(j != father) dfs(j,u,sum ^ c[i]);}}void insert(int x){int p = 0;for(int i = 30 ; ~i ; --i){int &s = trie[p][x >> i & 1];if(!s)s = ++tot;p = s;} }int query(int x){int res = 0 , p = 0;for(int i = 30 ; ~i ; --i){int s = x>>i & 1;if(trie[p][!s]){res += 1<<i;p = trie[p][!s];}else{res += 0<<i;// 沒有用,可以不用p = trie[p][s];}}return res; }int main(){scanf("%d",&n);memset(head,-1 ,sizeof head);for(int i = 0 , u , v , w ; i < n-1 ;++i){scanf("%d%d%d",&u,&v,&w);add(u,v,w),add(v,u,w);}dfs(0,-1,0);for(int i = 0 ;i < n ; ++i)insert(a[i]);int res = 0;for(int i = 0 ; i < n ; ++i){res = max(res, query(a[i]));}printf("%d\n",res);return 0; }二叉堆
題目鏈接
解題思路:
解題步驟:
代碼:
#include<stdio.h> #include<algorithm> #include<queue> #include<vector> #include<iostream> using namespace std; typedef pair<int,int>PII; const int N = 1E4 + 10;PII goods[N]; int n ;int main(){while(~scanf("%d",&n)){for(int i = 0,x,y ; i < n ; ++i){ scanf("%d%d",& x, &y);goods[i].first = y; // 因?yàn)閜air 自帶的是先以第一關(guān)鍵字排序,所以將時(shí)間放到goods[i].second = x;}sort(goods,goods + n);priority_queue<int,vector<int>,greater<int>>sale;for(int i = 0 ; i < n ;++i){int day = goods[i].first , value = goods[i].second;if(day > sale.size())sale.push(value);else if (day == sale.size() && value > sale.top()){sale.pop();sale.push(value);}}int res = 0;while(!sale.empty()){res += sale.top();sale.pop();}printf("%d\n",res);}return 0; }序列
題目鏈接
解題思路:
最暴力的做法當(dāng)然是枚舉和的所有可能,不過這數(shù)據(jù)范圍必爆呀。那么我們可以思考一下我們是否可以先從前面兩個(gè)序列里選出和最小的n個(gè)數(shù),然后這n個(gè)數(shù)繼續(xù)與后邊的進(jìn)行合并,一共合并m-1次。那么最關(guān)鍵的就是如何在兩個(gè)序列中找出和最小的n個(gè)和。如下圖,先對(duì)第一組進(jìn)行排序,然后分組
在這里因?yàn)閍是已經(jīng)排好序的,所以每一組的大小關(guān)系都是從小到大的。每一組的第一個(gè)元素之中的最小值就是最小值。因此我們將每一組的第一個(gè)元素放入小跟堆里進(jìn)行維護(hù)。在選出一個(gè)后刪除,再添加的時(shí)候是添加該組里邊的下一個(gè)位置。有以下技巧: s - a[p] + a[p+1]
代碼:
#include<stdio.h> #include<algorithm> #include<queue> #include<vector>using namespace std; typedef pair<int,int>PII;const int N = 2010; int m , n ; int a[N],b[N],c[N];void merge(){priority_queue<PII,vector<PII>,greater<PII>>heap;for(int i = 0 ; i < n ; ++i)heap.push({b[i] + a[0],0});for(int i = 0 ; i < n ; ++i){auto t = heap.top();heap.pop();int s = t.first , p = t.second;c[i] = s;heap.push({s - a[p] + a[p+1] , p + 1});}for(int i = 0 ; i < n ; ++i)a[i] = c[i];}int main(){int T ;scanf("%d",&T);while(T--){scanf("%d%d",&m,&n);for(int i = 0 ; i < n ; ++i)scanf("%d",&a[i]);sort(a,a+n);for(int i = 0 ; i < m-1 ;++i){for(int j = 0 ; j < n ; ++j)scanf("%d",&b[j]);merge();}for(int i = 0 ; i <n ;++i)printf("%d ",a[i]);puts("");}return 0; }數(shù)據(jù)備份
題目鏈接
解題思路:
由于還沒有證明出結(jié)論的正確性,先用上別的大佬的,
出處:https://www.acwing.com/solution/content/29786/
難點(diǎn):
代碼:
#include<stdio.h> #include<algorithm> #include<set>using namespace std; typedef long long LL; typedef pair<LL,int>PLI; const int N = 1E5 + 10; int l[N] , r[N]; LL d[N]; int n , k ;void delete_node(int x){r[l[x]] = r[x];l[r[x]] = l[x]; }int main(){scanf("%d%d",&n,&k);for(int i = 0 ; i < n ; ++i)scanf("%lld",&d[i]);for(int i = n - 1 ; ~i ; --i)d[i] = d[i] - d[i-1];d[0] = d[n] = 1e15;set<PLI>heap;for(int i = 1 ; i < n ; ++i){l[i] = i - 1 , r[i] = i + 1;if( i >= 1 && i < n )heap.insert({d[i],i});}LL res = 0;while(k--){auto t = heap.begin();LL v = t -> first ;int p = t -> second , left = l[p] , right = r[p];heap.erase(t);heap.erase({d[left],left}) , heap.erase({d[right],right});delete_node(left) , delete_node(right);res += v;d[p] = d[left] + d[right] - d[p];heap.insert({d[p],p});}printf("%lld\n",res);return 0; }合并果子 (二叉哈夫曼樹)
題目鏈接
二叉哈夫曼樹的創(chuàng)建步驟:
代碼:
#include<stdio.h> #include<algorithm> #include<queue> #include<vector>using namespace std;const int N = 10010; int n ; int main(){scanf("%d",&n);priority_queue<int,vector<int>,greater<int>>fruits;for(int i = 0 ,x ; i < n; ++i){scanf("%d",&x);fruits.push(x);}int res = 0;while(fruits.size() > 1){int fruit1 = fruits.top(); fruits.pop();int fruit2 = fruits.top() ; fruits.pop();res += fruit1 + fruit2;fruits.push(fruit1 + fruit2);}printf("%d\n",res);return 0; }荷馬史詩(k叉哈夫曼樹)
題目鏈接
解題思路:
k叉哈夫曼樹的創(chuàng)建步驟
代碼:
#include<stdio.h> #include<algorithm> #include<queue> #include<vector>using namespace std;typedef long long ll; typedef pair<ll,int>PLI;int n , k;int main(){scanf("%d%d",&n,&k);// 應(yīng)為用了pair 會(huì)先以first 為第一關(guān)鍵字從小到大排好序//如果相同會(huì)以second 作為第二關(guān)鍵字從小到大排序//而這就恰好貪心處理了在權(quán)值相同的情況下優(yōu)先選 節(jié)點(diǎn)深度(seocnd)小的節(jié)點(diǎn)priority_queue<PLI,vector<PLI>,greater<PLI>>heap;for(int i = 0 ; i < n ; ++i){ll x;scanf("%lld",&x);heap.push({x,0});}while((n-1)%(k-1)) heap.push({0,0}) , n++;ll res = 0;while(heap.size() > 1){ll s = 0;int depth = 0;for(int i = 0 ; i < k ; ++i){auto t = heap.top();heap.pop();s += t.first;depth = max(depth , t.second);}res += s;heap.push({s,depth + 1});}printf("%lld\n%d\n",res,heap.top().second);return 0 ; }小結(jié)
小知識(shí)點(diǎn)
解題思路
一般來說先想一下用暴力什么做,之后看有那些數(shù)據(jù)結(jié)構(gòu)或者算法可以對(duì)其進(jìn)行優(yōu)化。(如在暴力做的時(shí)候發(fā)現(xiàn)一些性質(zhì))
由數(shù)據(jù)范圍反推算法復(fù)雜度以及算法內(nèi)容
來自:https://www.acwing.com/blog/content/32/
總結(jié)
以上是生活随笔為你收集整理的基本数据结构篇(三万字总结)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基本算法总结篇
- 下一篇: 计算机基本信息的获取