基本数据结构篇(三万字总结)
數據結構
- 棧
- 編輯器(對頂棧)
- 火車進棧
- 火車進棧問題(卡特蘭數)
- 大數相乘
- 分解質因數
- 階乘分解質因數
- 壓位
- 配套的高精度除法
- 隊列
- 小組隊列
- 蚯蚓
- 雙端隊列
- 最大子序和(單調隊列)
- 哈希
- 雪花雪花雪花(序列的最小表示)
- 兔子與兔子
- 回文子串的最大長度
- KMP
- 周期(next 數組的性質)
- Trie
- 前綴統計
- 最大異或對
- 最長異或值路徑
- 二叉堆
- 序列
- 數據備份
- 合并果子 (二叉哈夫曼樹)
- 荷馬史詩(k叉哈夫曼樹)
- 小結
- 小知識點
- 解題思路
- 由數據范圍反推算法復雜度以及算法內容
棧
編輯器(對頂棧)
原題鏈接
解題思路:
解題思路鏈接: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提交,所以是多組數據{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的前綴和,加上這個一個新來的,構成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')//左傾思想(博古+文化大革命)(手動滑稽)if(!a.empty())//只要不為空b.push(a.top()),a.pop();//a+b等于整個插入序列,b負責管理當前光標右邊的序列.if (ch=='R')//右傾思想(陳獨秀)(手動滑稽){if (!b.empty())//b不為空{a.push(b.top());//a負責管理1~當前光標.所以現在a往右了,那么必然是要加入b棧的開頭,因為b棧管理當前光標的右邊.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]);//輸出當前最大值區間.}getchar();//換行符讀入}}return 0; }代碼鏈接:https://www.acwing.com/solution/content/1275/火車進棧
題目鏈接
解題思路:
如下圖,我們在dfs()的時候要維護三個狀態,狀態一就是出棧的序列,狀態二是還在棧里的元素,狀態三是還沒有進棧的元素。我們在每一個狀態下都有兩個操作可以進行,第一個操作是將元素進棧,第二個操作是元素出棧。邊界條件是state1 的元素個數為n。題目中要我們按字典序的輸出方案,因此我們考慮以下操作一與操作二的順序。因為state3中的元素肯定是比state2中的元素還要大的,因此我們先執行操作二再執行操作一即可。
代碼:
#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 要滿足的是元素個數不能大于nif(state3 <= n){state2.push(state3);state3 ++ ;dfs();state3 -- ;state2.pop();} }int main(){scanf("%d",&n);dfs();return 0; }火車進棧問題(卡特蘭數)
原題鏈接
解題思路:
本題是一個卡特蘭數,相關解釋可以百度。卡特蘭數的本質是:任何前綴某一類的元素大于等于另一類,如果題目符合這個性質,那么大概率就是卡特蘭數了。
首先我們要求高精,(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!? 最快的辦法是求出這玩意所有的質因數及其次數,然后求所有數乘積即可。同時在高精度乘法的時候壓位來提高速度。
解題步驟:
- 利用素數篩法,求出 2~2n 內的所有素數。
- 求出 2n! 與 n! 中質因子的個數。
- 減去 n + 1 中質因子個數。
- 大數乘法。
知識點:
大數相乘
分解質因數
階乘分解質因數
壓位
代碼:
#include<stdio.h> #include<vector>using namespace std;const int N = 1200010; int primes[N],cnt; bool st[N]; int power[N]; //素數篩法 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 因子的個數 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; // 進位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];// 對于計算·每一個質因子 在 n !中的個數power[p] = get(2*n,p) - get(n,p) * 2;}// 對分母的 n + 1 進行質因數分解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);// 對 還有的質因數進行乘法運算for(int i = 0 ; i < cnt ; ++i){int p = primes[i];for(int j = 0; j < power[p] ; ++j) // 質因數個數mutil(ans,p);}out(ans);return 0; }配套的高精度除法
void div(vector<int>&a,int b){int t = 0; // 上一位的余數·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(); }隊列
小組隊列
題目鏈接
解題思路:
因為隊伍中只要前邊有自己隊員就不用在隊尾排隊,所以如果我們用一個對列來實現的話不好弄。因為插入隊員后也是一個隊列。因為我們會每一個小組建立一個隊列。而排隊的隊列我們存的是組號。當我們要出隊時,我么就查詢對首值得到其對應的組號,依據這個組號去改組號對應的隊列里邊進行出隊。
- 進隊操作:當一個編號為x的隊員進隊時,我們得到其對應的組號。然后壓入該組號對應的隊列里邊,如果壓入之前隊列為空,那么將該組號壓入 q0 的隊尾。
- 出隊操作:我們讀取q0 中對首元素對應的組號,將這個組號的隊列的對首元素出隊。如果在出隊后隊列為空,那么就將q0的對首元素出隊,表示這個組的成員已經全部出隊了。
代碼:
#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; // 對于同一組的打上標簽}}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]; // 找到編號對應得組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; }蚯蚓
題目鏈接
解題思路:
我們發現如果蚯蚓i被切斷后,然后蚯蚓j也被切斷,那么我們發現,i蚯蚓的切后兩段的長度,都會大于j蚯蚓的切后的兩段的長度,因此這里有單調遞減的性質.
找到了性質,那么我們完全可以通過三個隊列模擬優先隊列,一個隊列維護切后的第一段,一個隊列維護切后的第二段,另外一個隊列,里面存儲蚯蚓長度,記住長度是從高到低,排好序的長度,那么每一次將被切斷的蚯蚓,肯定是這三個隊列的隊頭,因為我們這道題目具有單調遞減的性質,所以其實這道題目三個隊列,都隱藏著單調隊列的性質.
解題步驟
第一步將原始的長度從大到小排個序。之后進行m次操作。每一次取出三個隊列對首的最大值x,x在加上偏移量delta后,分成兩段后,偏移量加上q后,分別減去偏移量后放進隊列中。
技巧:
- 在本題中有一個對集合進行整體加上一個數的操作。0(n)的思路是把每一個元素都加上。不過有一個O(1)操作是,設置一個偏移量delta,存在集合中的數據是一個相對值,在取出后加上偏移量就得到真實值。然后在放入集合的時候減去偏移量即可。
代碼:
#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; }雙端隊列
題目鏈接
題解:
以下的大佬總結得比我好,所以就貼上了
題解出處
收獲:
- 找出元素相同的區間,while(j < n && a[i].first == a[j].first) j++;
- 貪心來維護單調性,同時設置一個變量來保存當前的單調性。
代碼:
#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; // 在接的時候是反過來接的,這樣才可以讓接的這一段單調減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; }最大子序和(單調隊列)
題目鏈接
解題思路:
簡單來說,我們先利用前綴和數組,將題目轉變為數組中兩個元素位置相距不大于m的情況下使得后者減去前者得到的值最大。那么我們就轉變為每遍歷一個右端點的時候,找到前m個元素的最小值。這樣就是找到了以該右端點的最大值。如果平常思路是遍歷前m個元素找到最小值。不過我們通過單調遞增隊列維護一個不超過m個元素的隊列。該隊列的對首的小標就是以i為右端點前m個元素中最小值所在的下標。
代碼:
#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 個元素的位置res = max(res,sum[i] - sum[q[hh]]);// 因為要維護一個單調遞增隊列。while(hh <= tt && sum[q[tt]] >= sum[i]) tt--; // 因為這里求值就是利用前綴和來的,可以把前綴和看成一個元素就好q[++tt] = i;}printf("%lld\n",res);return 0; }哈希
雪花雪花雪花(序列的最小表示)
題目鏈接
本題大致題意:
給了n和長度為6的序列,判斷兩個序列是否相識。可以進行的操作是旋轉:即將序列的第一個放到最后一個。另一個操作是翻轉。
正確解法應該是用哈希的,不過可以用別的方法解決。本文介紹另一種解法。哈希解法
解題思路:
先介紹以下 序列的最小表示
長度為n的序列的最小表示:一個元素為n的序列旋轉n次為得到原序列。在旋轉過程中產生的序列中,字典序最小的序列就是該序列的最小表示。
因此,如果本題的操作只有旋轉,我們依次求出每一個序列的最小表示。比較是否有相同的,有則說明有相似的序列。不過本題還有一個翻轉操作,那么我們同時還求出序列翻轉之后的最小序列。在這兩個序列中找最小的,比較其是否相同。
技巧:
代碼:
#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]); }// 求長度為6 的序列的最小表示 void get_min(int a[]){static int b[12];// 復制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; }兔子與兔子
題目鏈接
解題思路:
這題沒啥好說的了,就是一個字符串哈希的模板題。
代碼:
#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; }回文子串的最大長度
題目鏈接
題解:
來自:https://www.acwing.com/solution/content/30482/
收獲:
注意:本題二分,是找 小于等于一個半徑長度r的最大的一個(即r或r的前驅)所以使用的二分方法是:
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 數組的性質)
題目鏈接
題解出處:https://www.acwing.com/solution/content/28244/
代碼:
#include<stdio.h>const int N = 1E6 + 10; char str[N]; int Next[N]; int n ; // 求next數組模板。 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 ;} }// 對于KMP 一般下標從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
前綴統計
題目鏈接
題目大意:先給了n個字符串,之后給m個字符串,在這m個字符串中詢問其前綴出現在n個字符串的個數。暴力的做法就是對于每一個前綴依次與n個字符串進行比較O(n2)的。而這里涉及到了字符串得匹配問題,而trie樹恰可以解決這個問題。我們利用前n個字符串建立一個Trie樹,之后對于m個字符串,依次將這個字符串放到Trie樹里邊,沿著其前綴走一篇,如果經過的一個節點又結尾標記,就加上1.
題解:
- 記錄結尾個數的方法就是,額外建立一個數組,將每一次插入后最后的指針p,對應的位置加1.
對于查找字符串是否存在的話,
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] ++; // 如果只是做一個結尾標記的話可以用bool 數組,這里設置為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;}最大異或對
題目鏈接
解題思路;
(暴力枚舉) O(n2)
優化算法:
(trie樹)
trie樹中要明確兩個問題:
son[N][x]是個啥?idx是個啥?
首先son[N][x]這是個二維數組。
第一維N是題目給的數據范圍,像在trie樹中的模板題當中N為字符串的總長度**(這里的總長度為所有的字符串的長度加起來)**,在本題中N需要自己計算,最大為N*31(其實根本達不到這么大,舉個簡單的例子假設用0和1編碼,按照前面的計算最大的方法應該是4乘2=8但其實只有6個結點)。
第二維x代表著兒子結點的可能性有多少,模板題中是字符串,而題目本身又限定了均為小寫字母所以只有26種可能性,在本題中下一位只有0或者1兩種情況所以為2。
而這個二維數組本身存的是當前結點的下標,就是N嘍,所以總結的話son[N][x]存的就是第N的結點的x兒子的下標是多少,然后idx就是第一個可以用的下標。
鏈接: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; // 創建新節點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二進制第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; }最長異或值路徑
題目鏈接
解題思路:
難點:本題在上題的基礎上,應該是dfs是一個難點。因為可能是一個多叉樹,所以我們通過建圖來dfs。同時因為是一個無向圖,一個節點的出邊有一條是連向其父節點的,因此我們為了避免這種情況在dfs的時候把父節點的信息帶過來。
代碼:
#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 放的是邊的終點,c是邊權, 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;// 遍歷父節點的所有出邊for(int i = head[u] ; ~i ; i = Next[i]){int j = e[i]; // 下一個節點// 不能返回,即不看是其父節點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; // 因為pair 自帶的是先以第一關鍵字排序,所以將時間放到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; }序列
題目鏈接
解題思路:
最暴力的做法當然是枚舉和的所有可能,不過這數據范圍必爆呀。那么我們可以思考一下我們是否可以先從前面兩個序列里選出和最小的n個數,然后這n個數繼續與后邊的進行合并,一共合并m-1次。那么最關鍵的就是如何在兩個序列中找出和最小的n個和。如下圖,先對第一組進行排序,然后分組
在這里因為a是已經排好序的,所以每一組的大小關系都是從小到大的。每一組的第一個元素之中的最小值就是最小值。因此我們將每一組的第一個元素放入小跟堆里進行維護。在選出一個后刪除,再添加的時候是添加該組里邊的下一個位置。有以下技巧: 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; }數據備份
題目鏈接
解題思路:
由于還沒有證明出結論的正確性,先用上別的大佬的,
出處:https://www.acwing.com/solution/content/29786/
難點:
代碼:
#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; }合并果子 (二叉哈夫曼樹)
題目鏈接
二叉哈夫曼樹的創建步驟:
代碼:
#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叉哈夫曼樹的創建步驟
代碼:
#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);// 應為用了pair 會先以first 為第一關鍵字從小到大排好序//如果相同會以second 作為第二關鍵字從小到大排序//而這就恰好貪心處理了在權值相同的情況下優先選 節點深度(seocnd)小的節點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 ; }小結
小知識點
解題思路
一般來說先想一下用暴力什么做,之后看有那些數據結構或者算法可以對其進行優化。(如在暴力做的時候發現一些性質)
由數據范圍反推算法復雜度以及算法內容
來自:https://www.acwing.com/blog/content/32/
總結
以上是生活随笔為你收集整理的基本数据结构篇(三万字总结)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基本算法总结篇
- 下一篇: 计算机基本信息的获取