目錄node
圖 G 是一個二元組(V,E),其中V稱爲頂點集,E稱爲邊集。它們亦可寫成 V(G)和E(G)。E的元素是一個二元組數對,用(x,y)表示,其中x,y∈V。ios
①鄰接表 ②鏈式前向星 ③鄰接矩陣
①若把圖 G 全部頂點的度數排成一個序列 S,則稱 S 爲圖 G 的度數序列。
②考慮無向圖,d1, d2, ... ,dn表示每一個點的度數,d1 + d2 + ... + dn= 2e, 每條邊被計算兩次,一共有偶數個度數奇數的點。算法
給定一串有限多個非負整數組成的序列,是否存在一個簡單圖使得其度數列恰爲這個序列。令S=(d1,d2,...,dn)爲有限多個非負整數組成的非遞增序列。 S可簡單圖化當且僅當有窮序列S’=(d2-1,d3-1,...,d(d1+1)-1,d(d1+2),...,dn)只含有非負整數且是可簡單圖化的。數組
令S=(d1,d2,...,dn)爲有限多個非負整數組成的非遞增序列。S可簡單圖化當且僅當這些數字的和爲偶數,而且對任意1<=k<=n都成立。
即公式:spa
Tree Edge指樹邊 Back Edge指連向祖先的邊 Forward Edge指連向子孫的邊 Cross Edge指連向樹其餘分支的邊 在無向圖中只存在Tree Edge和Back
無向圖有歐拉回路的充要條件爲每一個點度數都是偶數且邊連通(注意孤立點)。有向圖有歐拉回路的充要條件爲每一個點的 入度 = 出度 且邊連通。一個聯通無向圖,有2k個奇數點,那麼須要k條路徑。3d
inline void dfs(int u) { for(int i=head[u];i;i=head[u]) { while (i && vis[abs(s[i])]) i=e[i].nxt; head[u]=i; if(i) { vis[abs(s[i])]=1; dfs(v[i]); q[++top]=s[i]; } } }
擬陣(E,I)知足I的每一個元素爲E的子集。 空集屬於I 若是A屬於I,那麼A的全部子集也屬於I 若是A,B屬於I,而且|A|>|B|,那麼存在一個A中不屬於B中的元素u,知足B∪{u}也屬於I。 這樣就被稱爲擬陣,對於擬陣,咱們可使用貪心算法從小到大或者從大到小選擇。 令E爲邊集,I爲全部生成森林的集合,那麼(E,I)爲擬陣。 常見的擬陣還有匹配擬陣和線性無關組。
一開始每一個連通份量是一個點自己。每輪每一個連通份量選擇和其餘連通份量相連的最小的邊,而後合併。時間複雜度O(E log V)rest
使得生成樹樹上最大邊權值最小。
方法1: 最小生成樹必定是最小瓶頸生成樹。
方法2: 二分答案,看點是否連通。
類比找第k大值的方法,首先隨機一個邊權w。
而後將不超過這個邊權的邊加入,遍歷這張圖。
若是圖連通,那麼瓶頸不超過w,因而只需考慮邊權不超過w的邊。
不然將這些連通點縮起來,考慮邊權大於w的邊。
每次將問題的規模縮小至一半。
指望時間複雜度O(m)。code
1.Prufer序列:一棵樹要獲得Prufer序列,方法是逐次去掉樹的頂點,直到剩下兩個頂點。考慮樹T,其頂點爲{1, 2, ... ,n}。在第i步,去掉標號最小的葉子,並把Prufer序列的第i項設爲這葉的鄰頂點的標號。一棵樹的序列明顯是惟一的,並且長爲n − 2。blog
2.復原:設這Prufer序列長n − 2。首先寫出數1至n。找出1至n中沒有在序列中出現的最小數。把標號爲這數的頂點和標號爲序列首項的頂點連起來,並把這數從1至n中刪去,序列的首項也刪去。接着每一步以1至n中剩下的數和餘下序列重複以上步驟。最後當序列用完,把1至n中最後剩下的兩數的頂點連起來。排序
3.Cayley定理:徹底圖的生成樹個數爲n^(n-2)次。若是每一個點的度數爲di,那麼生成樹個數爲(n-2)!/(d1-1)!/(d2-1)!/.../(dn-1)!每一個連通塊大小爲ai,那麼添加一些邊將這些連通塊連通的生成樹個數爲a1a2...an(a1+a2+...+an)^(n-2)次。
令G=D-A,而後去除G的任意一行一列G’,G’的行列式即生成樹個數。有向圖計數,即樹形圖個數。這裏的D變爲每一個點的入度,刪去第i行第i列爲從第i個點出發的樹形圖個數。
Dijkstra/Bellman Ford算法(時間複雜度O(m log n)或者O(nm))
Floyd算法(O(n^3))/ Johnson算法(負權)((nm log n))
Dijkstra 貪心
Bellman Ford 動態規劃
邊權是0/1
雙端隊列,若是是0在頭部插入,不然在尾部插入。
總長不超過W, 正權
使用0..W的桶+鏈表維護這些點,時間複雜度O(m+W)。
根據最短路有不等式dis(v)<=dis(u)+w(u,v),剛好存在一個這樣的u知足條件。而且這樣計算出來的dis(v)是最大的。,對於一些s(v)<=s(u)+w(u,v)的限制,能夠類比最短路建圖。
判斷解惟一時,對原圖求一遍最短路。將原圖取反,邊權取反,求一遍最長路。一個標號對應的是能取到的最小值,一個是最大值。若是相同則解惟一。
首先給圖中每一個點一個標號h(v), 把每條邊(u,v)邊權改爲w(u,v)+h(u)-h(v)。對於s-t的一條路徑p,權值爲
因此不會改變最短路。 從1號點出發跑一遍最短路,記h(v)=dis(v)。 由不等式能夠獲得dis(u)+w(u,v)>=dis(v),也就是改完以後邊權非負。 以後能夠每一個點用Dijkstra跑。
u的偏愛距:ecc(u)=max dis(u,v) 直徑 d=max ecc(u) 半徑 r=min ecc(u) (d≠2r) 中心 arg min ecc(u) (要求定義在點上) 絕對中心 (能夠定義在邊上)
固定一條邊(u,v),考慮上面的點p的偏愛距。 假設第三個點是w, dis(p,u)=x 那麼對應的折線爲 min(x+dis(u,w), w(u,v)-x+dis(v,w))。 那麼偏愛距爲n條折線的最大值造成的折線。 按左端點排序維護一下。 時間複雜度O(nm log n)
絕對中心的最短路樹證實:
注意一棵樹T有直徑爲半徑的兩倍(對絕對中心來講)。 若是最小直徑生成樹T’不包含絕對中心,那麼取T’的絕對中心v,顯然矛盾。
每次去掉圖中入度爲0的點。時間複雜度O(n+m)。若是最後不爲空集那麼這個圖不爲DAG。 不然每一個點入度不爲0,即每一個點能夠選擇一個前趨,沿着前趨走根據抽屜原理必定能找到相同點,也就是一個環。
每一個點有不一樣的標號,要使得拓撲序最小。將拓撲排序的隊列改爲優先隊列便可。
使得最後的拓撲序中1的位置儘可能靠前,若是相同比較2的位置,依次類推。首先考慮如何求1最先出現的位置,能夠將原圖反向,而後每次彈除了1以外的元素,直到隊列只剩下1爲止。這是反圖中1的最晚的出現的位置,也就是原圖中最先的。根據是否在隊列裏,這個圖被分紅兩部分,在對應的圖中用一樣的方法處理2,依次類推。容易發現每次找儘可能大的元素出隊,能完成上述的過程。因此等價於反圖最大字典序。
對於一個二分圖G=(X,Y,E),記S爲X的一個子集,N(S)爲全部S中全部點鄰居的並集。
一個圖有完備匹配當且僅當X的全部子集S都有|S|<=|N(S)|
對通常圖的推廣:
推論: 每一個正則二分圖都有完備匹配。
最小點覆蓋=最大匹配 (與最大流最小割定理等價)
最大獨立集=點數-最大匹配 (獨立集爲點覆蓋的補集)
最小邊覆蓋=最大獨立集 (獨立集中每一個點須要一條邊去覆蓋)
覆蓋全部的邊: 每條邊下界設爲1, 而後求最小流。
覆蓋全部的點: 創建二分圖,對於u->v的邊,看作二分圖中的(u,v’),而後答案爲點數-最大匹配。
Dilworth 定理: 最大反鏈=最小鏈覆蓋
Tarjan:
首先每一個點根據DFS的時候訪問的順序進行標號,記做這個點的時間戳。
而後每一個點維護一個low值,即這個點經過Tree edge和Back edge能訪問到時間戳最小的點。
若是一個點的能訪問到最先的點爲這個點,就會造成一個新的強連通份量。
一個圖將強聯通份量縮起來將會造成一個DAG。
void tarjan(int pos){ vis[stack[++index]=pos]=1;//入棧並標記 LOW[pos]=DFN[pos]=++dfs_num; for(int i=pre[pos];i;i=E[i].next){ if(!DFN[E[i].to]){ tarjan(E[i].to); LOW[pos]=min(LOW[pos],LOW[E[i].to]); } else if(vis[E[i].to]) LOW[pos]=min(LOW[pos],DFN[E[i].to]); } if(LOW[pos]==DFN[pos]){ vis[pos]=0; size[dye[pos]=++CN]++;//染色及記錄強連通份量大小 while(pos!=stack[index]){ vis[stack[index]]=0; size[CN]++;//記錄大小 dye[stack[index--]]=CN;//彈棧並染色 } index--; } }
點連通度: 最小的點集使得刪去以後圖不連通
邊連通度: 最小的邊集使得刪去以後圖不連通
若是一個圖的點連通度大於1,那麼是點雙連通的,邊連通同理。
雙聯通份量爲圖中的極大雙聯通子圖。
考慮DFS樹,每條非樹邊對應着一個點到祖先的路徑。對於一條非樹邊只要把對應的邊打上標記便可。好比對於(u,v)這條非樹邊,只要在u點打上+1的標記,v點打上-1的標記。v到v的父親的樹邊的覆蓋次數爲子樹內全部標記的和。割點同理(注意特判根節點和葉節點)。
注意打標記這個過程能夠在線完成。可使用一個並查集維護當前雙聯通份量中的點,記錄一下每一個雙聯通份量中最高的點。而後對於一條非樹邊,暴力將這些點合併起來便可。由於一條邊最多被合併一次,須要不超過O(m)次的並查集操做。邊雙聯通份量縮完以後會造成一棵樹。
一堆變量的二元限制,問是否存在合法的賦值。
首先每一個變量拆兩個點,Xi和Xi’表示Xi=1或0對於Xi or Xj這樣的限制,從Xi’向Xj連邊,從Xj’向Xi連邊,表示若是Xi取0,那麼Xj要取1,反之亦然。同時對於Xi=1這樣的限制能夠轉化爲Xi or Xi,因而從Xi’向Xi連邊,表示不能取Xi’。對於這樣的圖求強連通份量。有解的充要條件爲對於每一個變量Xi和Xi’不在同一個強連通份量裏。求方案的時候,對於一個變量Xi和Xi’,只要取Tarjan算法中強連通份量早造成的便可。感性認識: 若是Xi能到達Xi’,那麼Xi’的強連通份量會早造成。
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; const int N=1e5+10; const int M=63; int m,x,len,l,t; int a[N],ans[N],js[7],f[M+2][N]; char s[N],ch[9]; inline int read() { int n=0,f=1;char ch=getchar(); while (ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();} while (ch<='9' && ch>='0') {n=(n<<3)+(n<<1)+ch-'0';ch=getchar();} return n*f; } inline int work(int x) { for(int k=0;k<=M;++k) { int jc=0; for(int i=0;i<6;++i) if((k>>i)&1) jc+=js[i]; if(f[k][len]-f[k][x]<jc) return 0; } return 1; } int main() { scanf("%s%d",s+1,&m); len=strlen(s+1); //預處理一下 for(int i=1;i<=len;++i) { a[i]=M,ans[i]=-1; ++js[s[i]-'a']; } for(int i=1;i<=m;++i) { scanf("%d%s",&x,ch); l=strlen(ch),t=0; for(int j=0;j<l;++j) t|=1<<(ch[j]-'a'); a[x]&=t; } for(int k=0;k<=M;++k) for(int i=1;i<=len;++i) f[k][i]=f[k][i-1]+(bool)(a[i]&k); for(int i=1;i<=len;++i) { for(int j=0;j<6;++j) { --js[j]; if(((a[i]>>j)&1) && work(i)) { ans[i]=j; break; } ++js[j]; } if(ans[i]==-1) { printf("Impossible\n"); return 0; } } for(int i=1;i<=len;++i) cout<<char(ans[i]+'a'); return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<vector> #include<queue> #include<algorithm> using namespace std; const int N=2e5+10; int n,x,y,res; int vis[N],head[N]; vector<int> g[N],ans; queue<int> q; struct node { int a,b,nxt; }e[N]; inline int read() { int n=0,f=1;char ch=getchar(); while (ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();} while (ch<='9' && ch>='0') {n=(n<<3)+(n<<1)+ch-'0';ch=getchar();} return n*f; } inline int cmp(int x,int y) { return e[x].b<e[y].b; } inline void init() { for(int i=1;i<=n;++i) e[e[i].a].b=i; for(int i=1;i<=n;++i) { sort(g[i].begin(),g[i].end(),cmp); for(int j=0;j<g[i].size();++j) res=g[i][j]; } } inline void bfs(int s) { q.push(s); vis[s]=1; while (!q.empty()) { int u=q.front(); q.pop(); ans.push_back(u); for(int i=0;i<g[u].size();++i) { int v=g[u][i]; if(vis[v]) continue; vis[v]=1; q.push(v); } } } inline int pd() { for(int i=0;i<ans.size();++i) if(e[i+1].a!=ans[i]) return 0; return 1; } int main() { n=read(); for(int i=1;i<=n-1;++i) { x=read(),y=read(); g[x].push_back(y); g[y].push_back(x); } for(int i=1;i<=n;++i) e[i].a=read(); if(e[1].a!=1) { printf("No"); return 0; } init(); bfs(1); if(pd()) printf("Yes"); else printf("No"); return 0; }
題目簡述:
有n個航班依次起飛,一個時刻只能有一個飛機起飛,而且有m個限制:第一種限制,第i個飛機必須在c(i)的時刻前起飛。第二種限制,第i個飛機必須在第j個飛機以前起飛。
詢問:
一個可行的起飛方案。每一個飛機最先的起飛時間。n<=2e3, m<=1e4
Solution
倒過來變成每一個飛機在某個時刻以後能夠起飛。 第二問變成每一個飛機最晚何時起飛。 直接用拓撲排序的作法便可。
題目簡述:
給你一個有向圖,每一個點都標有01。問是否對於全部01串,都存在一條路徑,使得將路徑上通過的點的數字連起來獲得01串。
Solution
這裏只討論二分圖。 最大匹配: Hungarian/Hopcroft-Karp/Dinic 最大權匹配: KM/費用流 判斷是否存在奇環,只要看是否是二分圖便可。 判斷是否存在偶環,首先看每條非樹邊對應的環是否是偶環。 若是存在那麼就找到了偶環。 不然考慮若是兩個奇環相交,那麼去除中間部分就會造成一個偶環。 因此對於奇環的非樹邊只要暴力訪問樹邊打上標記,若是已經有標記了就說明存在奇環。 時間複雜度O(n+m)
題目簡述:
你有一個n個點m條邊的無向圖。(n, m<=2e5)
問是否存在兩個點,使得這兩個點之間有三條簡單路,而且這三條簡單路沒有公共點。
Solution
若是兩條非樹邊對應的環有交,那麼必定能夠找到這樣的兩個點。不然不存在。
題目簡述:
**有一個n*n的網格圖,上面有些格子可行,有些格子是障礙。(N<=1000 Q<=300000 )
有Q個詢問,想把一個正方形箱子從(r1,c1)推到(r2,c2),問箱子最大的大小。(起點終點是正方形箱子的中點)**
Solution
首先從障礙開始bfs,求出每一個格子最近的障礙。而後變成了求一條路徑,使得路徑上的最小值最大。求最大生成樹,而後在上面倍增詢問便可。
題目簡述:
你有6種字母,第i個字母有ci個。你要用這些字母排成一個字符串,其中有一些條件,第i個位置只能填某個字母的子集。問你能填出的字典序最小的字符串是什麼。(sum ci<=10^5)
Solution
首先求出最大匹配,下面考慮左邊點的狀況。咱們將匹配中的邊從右往左連,不在匹配中的邊從左往右連。這個時候一條增廣路成爲一條連續的路徑。從每一個左邊未匹配的點仍是遍歷,若是被一個左邊的點被訪問到,說明存在一條增廣路,也就是不必定在最大匹配中。全部沒有被訪問到的點必定在最大匹配中。
題目簡述:
給定一個n個點的二分圖,每條邊有一個邊權。找到一個邊權和最小的邊集,使得刪掉這個邊集以後不存在完備匹配。n<=20
Solution
根據Hall定理,只要存在一個集合S,使得|N(S)|<|S|,則不存在完備匹配。因而咱們枚舉S集合,而後貪心刪除邊集使得|N(S)|<|S|。
題目簡述:
有n個強盜,每一個強盜會在時刻l到時刻r搶劫,會形成c的損失。在一個時刻,你能夠選擇抓一個強盜,強盜被抓住以後不會形成損失。你要抓儘可能多的強盜使得損失儘可能小。(n<=5000)
Solution
按強盜從大到小排序,貪心選取每一個強盜能不能抓。判斷一些強盜能不能抓完,能夠按左端點排序,使用優先隊列維護右端點。貪心算法的正確性: 考慮匈牙利算法,從大到小一個一個匹配,一個點一旦在匹配中,那麼一直在匹配裏面。
題目簡述:
平面上有n個點(x_i,y_i),將這些點紅藍染色使得每行每列紅藍點個數的差不超過1。生成2^n的01串(這個串頭尾相連),使得全部長度爲n的01串都出現過。
題目簡述:
有n個點,每一個點有個權值ai,兩個點之間的邊權爲(ai+aj) mod M。問最小生成樹。(N<=1e5,0<=M,a_i<=1e9)