目錄html
遞歸定義算法
它或者是一棵空樹,或者是具備下列性質的二叉樹: 若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉搜索樹。數組
•維護一個集合,支持操做:插入、刪除、查找數據結構
•方法:創建一棵有序的二叉樹,每一個結點對應集合中一個元素。學習
•知足性質:結點u的左子樹的結點權值都比u小,右子樹的結點權值都比u大。優化
•插入、刪除、查找只需在二叉樹中按照權值往下走便可。spa
可能的問題:
可能退化成一條鏈(例如若是按升序插入),那麼每次操做平均複雜度O(n)須要一些技巧讓二叉搜索樹保持平衡。STL中的set和map都是平衡的二叉搜索樹。noip通常狀況下夠用。指針
護區間信息的數據結構code
每一個結點對應一個序列的區間htm
徹底二叉樹,左子樹爲左一半區間,右子樹爲右一半
單點/區間修改。
維護一個數據結構,支持如下兩種操做:
1.插入一個數x
2.查詢[l,r]區間中有多少個數
操做數<=10^5 x,l,r<=10^9
強制在線
用線段樹作
注意雖然inf=10^9很大,log(inf)仍是很小
操做數不多
每一個結點的值表示對應區間內的數的個數
每次操做至多增長\(O(logn)\)個非零結點
咱們只須要將這\(logn\)個非零結點開出來便可,空間複雜度\(O(nlog(inf))\)
查詢時,若是碰到沒有開出的結點,說明這個結點值爲\(0\),返回\(0\)便可
注意此時不能使用數組模擬二叉樹的方法(和堆相似,\(1\)爲根,\(2n-1\)和\(2n\)分別爲n的左右兒子) 由於得開\(10^9\)的數組,爆空間
得加上左右兒子指針的數組。
爲新開的結點賦予大於零的編號 \(o=++tot\);
查詢時遇到結點編號爲\(0\),就說明這個結點爲空,對應區間中沒有數
考慮都是動態開點的兩棵權值線段樹(結點權值爲對應區間的數的個數),如何將這兩棵線段樹合併?(即將他們對應的兩個數集合並)
tree *merge(int l, int r, tree *A, tree *B){ if(A == NULL) return B; if(B == NULL) return A; if(l == r) return new tree(NULL, NULL, A -> data + B ->data); int mid = (l + r) >> 1; return new tree(merge(l, mid, A -> ls, B -> ls), merge(mid + 1, r, A -> rs, B -> rs), A -> data + B -> data); }
複雜度:兩棵線段樹中重合結點(對應位置都開出來的結點)的個數。
一棵權值線段樹表示一個數集。
若是每棵權值線段樹對應的數集都只有一個數,那麼這棵權值線段樹只有\(O(log(inf))\)個開出的結點
\(n\)個這樣的權值線段樹合併,複雜度?
將n個這樣表示單元素集合的權值線段樹合併,複雜度不會高於將n個元素插入權值線段樹。
所以複雜度\(O(nlog(inf))\)
以以下方式給定一個有向圖:
給出結點量n與參數m
接下來m行,每行三個整數\(l_i,r_i,w_i\)
表示點xi向\([l_i,r_i]\)的每一個點連出一條長度爲\(w_i\)的邊
求1到n點的最短路
\(n,m<=10^5\)
對n個頂點創建一棵線段樹。
對於\(l_i,r_i,w_i\)
只需將\([l_i,r_i]\)分解成線段樹上\(log(n)\)個結點
\(x_i\)分別向這\(log(n)\)個結點連一條長度爲\(w_i\)的有向邊便可
邊數降爲\(O(mlogn)\)
感受線段樹的這些操做聽得一臉懵逼
記錄出\(size[i]\)表示這個集合的元素個數,合併時把元素個數小的合併到大的上
爲何要按秩合併?
衆所周知,並查集若是深度過深了,會致使查詢時複雜度退化因此就能夠經過記錄\(size\)來減小深度
void join(int x,int y) { x=find(x),y=find(y); if(size[x]<size[y]) f[x]=y,xize[y]+=size[x]; else f[y]=x,xize[x]+=size[y] }
黑白染色法
大體思路就是先找到一個沒被染色的節點u,把它染上一種顏色,以後遍歷全部與它相連的節點v,若是節點v已被染色而且顏色和節點u同樣,那麼就失敗了。若是這個節點v沒有被染色,先把它染成與節點u不一樣顏色的顏色,而後遍歷全部與節點v相連的節點............就這樣循環下去,直到結束爲止。
vector<int> q[N]; int col[N];//col[u]表示u的顏色,1爲白色,-1爲黑色,0爲未染色 bool flag=true; void dfs(int u,int c) { col[u]=c; for(int i=0;i<e[u].size();++i) { int v=e[u][i]; if(!col[v]) dfs(v,-c); else if(col[v]==c) flag=false; } } for(int i=1;i<=n;++i) if(!col[i]) dfs(i,1);
以前寫的筆記連接
長成這個樣子
設字母集爲\(\sum\),則每一個結點有至多\(|\sum|\)個兒子
例:給定\(n\)個字符串的集合,\(q\)個詢問,每一個詢問給出一個字符串,詢問集合中有沒有這個字符串
用\(trie\)作,設字符串總長度爲L,空間複雜度\(O(L*|\sum|)\),時間複雜度\(O(L)\)
int rt=1,cnt=1; int ch[N][27],sizes[N];//N是最長串 void ins(string s) { int o=rt; for(int i=0;i<s.size();++i) { k=s[i]-'a'; if(ch[o][k]) o=ch[o][k]; else ch[o][k]=++cnt,o=ch[o][k]; } sizes[o]++; } int query(string s)//查詢有沒有出現過 { int o=rt; for(int i=0;i<s.size();++i) { int k=s[i]-'a'; if(ch[o][k]) o=ch[o][k]; else return 0; } return 1; }
給定n個正整數,求這\(n\)個整數中兩兩異或的最大值?
\(n<=10^5\),每一個整數\(a_i<=10^9\)
把每一個正整數轉換爲一個\(30\)位的二進制串,分別插入一棵\(trie\)中
對於每個整數X,咱們想找到它與其它整數異或獲得的最大值
沿着\(trie\)從上往下走,採起貪心策略。
每一步設X的當前位爲\(b\),則優先往\(b^1\)方向走(這樣能夠在結果的這一位獲得一個1);若是\(b^1\)方向沒有兒子,則往\(b\)方向走
int ch[N*30][2]; int rt=1,cnt=1; void ins(int x) { int o=rt; for(int i=30;i>=0;--i) { int k=x>>i&1; if(ch[o][k]) o=ch[o][k]; else ch[o][k]=++cnt,o=cnt; } } int query(int x) { int ans=0; int o=rt; for(int i=30;i>=0;--i) { int k=x>>i & 1; if(ch[o][k^1]) ans|=1<<i,o=ch[o][k^1]; else o=ch[o][k]; } return ans; } int a[N],n; for(int i=0;i<n;++i) scanf("%d",a+i); for(int i=0;i<n;++i) ins(a[i]); int ans=0; for(int i=0;i<n;++i) ans=max(ans,query(a[i]));
string s; usigned long long v=0;//v是s的hash值 usigned long long p=1; for(int i=0;i<s.size();++i) { v+=p*(s[i]-'a'); p*=26ull; }
取兩個模數以後兩個模數的hash都出現過才行
把每位當作進制,有效避免了\(ab,ba\)判斷不出來的狀況
給定一個字符串,每次詢問給定四個數\(l_1,r_1,l_2,r_2\),知足\(r_1-l_1=r_2-l_2\)問
令\(f[i][j]\)表示從i開始,\(2^j\)個數的最小值
\(f[i][j]\)總共有\(nlogn\)個狀態
能夠由\(j\)從小到大遞推求出
\(f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);\)
感受這東西和倍增差很少
int a[N],f[N][20]; int lg[N]; void init() { for(int i=1;i<=n;++i) f[i][0]=a[i]; for(int j=1;(1<<j)<=n;++j) for(int i=1;i+(1<<j)-1<=n;++i) f[i][j]=min(f[i][j-1],f[i+(1<<j)][j-1]); for(int j=1;(1<<j)<=n;++j) lg[(1<<j)]=j; for(int j=1;j<=n;+j) if(!lg[j]) lg[j]=lg[j-1]; } int query(int l,int r) { int k=lg[r-l+1]; return min(f[l][k],f[l-(1<<k)+1][k]); }
\(f[i][j]\)表示\(i\)的第\(2^i\)祖先
inr lca(int u,int v) { if(dep[u]>dep[v]) swap(u,v); int k=dep[v]-dep[u]; for(int i=0;i<=lg[k];++i) if(k>>1&1) v=f[i][k]; if(u==v) return u; for(int i=lg[n];i>-0;--i) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i]; return f[u][0]; }
感受比較難
int seq[N<<1],cnt,pos[N]; //dfs序 int f[N<<1][20],dep[N]; //f[i][j]表示dfs序中從i位置開始2^j個結點中 深度最小的結點 void init() { for(int i=1;i<=cnt;++i) f[i][0]=seq[i]; for(int j=1;j<=LG[cnt];++j) for(int i=1;i+(1<<j)-1<=cnt;++i) f[i][j] = dep[f[i][j-1]]<dep[f[i+(1<<j-1)][j-1]]?f[i][j-1]:f[i+(1<<j-1)][j-1]; } int lca(int u,int v) { int a=pos[u],b=pos[v]; if(a>b) swap(a,b); int k=LG[b-a+1]; return dep[f[a][k]]<dep[f[b-(1<<k)+1][k]]?f[a][k]:f[b-(1<<k)+1][k]; }
ector<int> e[N],Q[N],id[N]; //e是邊表,Q[i]中保存的是與i點相關的查詢 int f[N],n; //f並查集 已初始化 int ans[N]; bool vis[N]; //dfs時是否通過 int find(int x){return x==f[x]?x:f[x]=find(f[x]);} void ins_query(int u,int v,int o) { //插入一個編號爲o的詢問,詢問(u,v)兩點的lca Q[u].push_back(v),id[u].push_back(o); Q[v].push_back(u),id[v].push_back(o); } void dfs(int u) { vis[u]=true; for(int i=0;i<Q[u].size();++i) { int v=Q[u][i]; //id[u][i]表示lca查詢(u,v)的編號 if(vis[v]) ans[id[u][i]]=find(v); } for(int i=0;i<e[u].size();++i) { int v=e[u][i]; if(vis[v]) continue; dfs(v); f[find(v)]=u; } }
\(n+\frac{n}{2}+\frac{n}{3}+\frac{n}{4}+......+\frac{n}{n}=n(1+\frac{1}{2}+\frac{1}{3}+......+\frac{1}{n})=n\times log(n)\)
那天沒事的時候來看看