圖論:你好,sugar,咱們終於又見面了!html
sugar:什麼,咱們見過面嗎??(天那!我居然一點都不記得!!)node
圖論:什麼,不記得我了。~~~~(>_<)~~~~寶寶表示很傷心。ios
sugar:sorry,那個,要不咱們再重新認識一下吧、、、、、算法
圖論:好吧。。。。。(憂傷的低下了頭,嗚嗚,人家但是很重要的,居然把人家忘了,哼!)數組
1、tarjan求強連通份量、縮點ide
http://www.cnblogs.com/z360/p/7010712.htmlspa
http://www.cnblogs.com/z360/p/7029416.html.net
Tarjan算法是一個經過對圖進行深度優先搜索並經過同時維護一個棧以及兩個相關時間戳的算法。 定義dfn(u)表示節點u搜索的次序編號(時間戳,第幾個搜索的節點),low(u)表示節點u或u的子樹當中能夠找到的最先的棧中的節點的時間戳。code
由定義,咱們能夠獲得: Low(u)=dfn(u), low(u)=min(low(u),low(v))當(u,v)爲搜索樹當中的樹枝邊。 low(u)=min(low(u),dfn(v))當(u,v)爲搜索樹當中的後向邊(也就是v仍在棧內的狀況) 每當找到一個節點在遍歷完後返回時low(u)=dfn(u),那麼能夠彈出棧中u以上的全部節點,此時這裏的全部節點造成一個強連通份量htm
當咱們在求出強連通份量以後,咱們能夠把同一個強連通份量裏的點當成新圖裏的一個新點,而後若是原圖中兩個不在同一個強連通份量的點有連邊的話,那麼他們所對應的新點也有連邊。新圖是一個有向無環圖
模板:
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N struct Edge { int from,to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int tarjan(int now)//tarjan求強連通份量,sum相同的在一個強連通裏 { dfn[now]=low[now]=++tim; stack[++top]=now,vis[now]=true; for(int i=head[now];i;i=edge[iw].next) { int to=edge[i].to; if(vis[to]) low[now]=min(dfn[to],low[now]); else if(!dfn[to]) tarjan(to),low[now]=min(low[to],low[now]); } if(low[now]==dfn[now]) { sum++;belong[now]=sum; for(;stack[top]!=now;top--) { belong[stack[top]]=sum; vis[stack[top]]=false; } vis[now]=false;top--; } } int shink_point()//縮點 { for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) { int x=edge[j].to; if(belong[i]!=belong[x]) add(belong[i],belong[x]); } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); shrink_point(); return 0; }
常見題型:1.給定一張有向圖,求加多少條邊以後整個圖是一張強連通份量。 若是一個圖是一個強連通份量的話,那麼他縮完點之後必定沒有入讀與出度爲零的點。這樣的話,咱們知道,咱們加上邊之後只要讓他知足出度跟入度都不爲0的話,那麼他就是一個強連通了。因此,咱們·加邊的數量就等於入度與出度爲零的點的最大值。
2.問從一個點發出的信號最少通過多長時間他從別人那收到。把每一個人說話的對象做爲這我的連出去的邊。那麼,咱們獲得一個N個點N條邊的圖。而且每一個點只有一個出邊。所以,圖必然是許多個相似的連通塊,而每一個連通塊都是一棵樹加一條邊,這條邊與本來的樹邊造成一個環。實際上咱們只須要找出最小環就能夠了。
3.咱們知道一些關係,問最少讓多少人知道信息可讓全部都知道(每一個人能夠將信息穿給他認識的人&&那我的願意相信他) 咱們tarjan縮點,而後求入讀爲0的點的個數就行了、、、、
2、tarjan求割邊割點
http://www.cnblogs.com/z360/p/7056604.html
割邊:對於無向連通圖來講,若是刪除一條邊以後使得這個圖不連通,那麼該邊爲割邊
割點:對於無向連通圖來講,若是刪除一個點以及與它相連的邊以後,那麼該點爲割點
1.割點的求法
對於一個點u,
1.u爲dfs搜索樹的根,若是搜索樹中它有大於一個的子節點,那麼爲割點;
2.u不爲dfs搜索樹的根,若是u存在子節點v使low[v]>=dfn[u],那麼u爲割點
2.割邊的求法
對於一條邊(u,v),
1.若是(u,v)是後向邊,必定不是割邊;
2.若是(u,v)是樹邊,那麼若是low[v]>dfn[u],那麼是割邊;
代碼:
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 200010 using namespace std; int n,m,x,y,tim,tot,ans; int low[N],dfn[N],vis[N],head[N],cut_edge[N],cut_point[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int from,to,next; }edge[N]; int add(int x,int y) { edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; tot++; } void tarjan(int now,int pre) { int sum=0;bool boo=false;vis[now]=true; dfn[now]=low[now]=++tim; for(int i=head[now];i;i=edge[i].next) { int t=edge[i].to; if((i^1)==pre) continue; if(!dfn[t]) { sum++;tarjan(t,i); low[now]=min(low[now],low[t]); if(low[t]>dfn[now]) cut_edge[i/2]=1; if(low[t]>=dfn[now]) boo=1; } else low[now]=min(low[now],dfn[t]); } if(!pre) {if(sum>1) ans++,cut_point[now]=true;} else if(boo) {ans++;cut_point[now]=true;} } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y),add(y,x); for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0); printf("%d\n",ans); for(int i=1;i<=n;i++) if(cut_point[i]) printf("%d ",i); return 0; }
3、最小生成樹
http://www.cnblogs.com/z360/p/6853637.html
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 using namespace std; int n,m,x,y,z,fa[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int x,y,z; }edge[N]; int cmp(Edge a,Edge b) { return a.z<b.z; } int found(int x) { if(x==fa[x]) return x; fa[x]=found(fa[x]); return fa[x]; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); edge[i].x=x; edge[i].y=y; edge[i].z=z; } sort(edge+1,edge+1+m,cmp); for(int i=1;i<=n;i++) fa[i]=i; int ans=0; for(int i=1;i<=m;i++) { x=edge[i].x;y=edge[i].x; int fx=found(x),fy=found(y); if(fx==fy) continue; fa[fx]=fy; ans+=edge[i].z; } printf("%d\n",ans); return 0; }
咱們求把全部的點都連起來所需的最小代價。
次小生成樹
http://www.cnblogs.com/z360/p/6875488.html
求次小生成樹的兩種方法
1:首先求出最小生成樹T,而後枚舉最小生成樹上的邊,計算除了枚舉的當前最小
生成樹的邊之外的全部邊造成的最小生成樹Ti,而後求最小的Ti就是次小生成樹。
2:首先計算出最小生成樹T,而後對最小生成樹上任意不相鄰的兩個點 (i,j)
添加最小生成樹之外的存在的邊造成環,而後尋找i與j之間最小生成樹上最長的邊刪去,
計算map[i][j](最小生成樹之外存在的邊) 與 maxd[i][j](最小生成樹上最長的邊)
差值,求出最小的來,w(T)再加上最小的差值就是次小生成樹了。
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 300010 using namespace std; int n,m,x,y,z,k,sum,tot,num,answer=N,fa[N],ans[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int x,y,z; }edge[N]; int cmp(Edge a,Edge b) { return a.z<b.z; } int find(int x) { if(x==fa[x]) return x; fa[x]=find(fa[x]); return fa[x]; } int main() { freopen("mst2.in","r",stdin); freopen("mst2.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); edge[i].x=x; edge[i].y=y; edge[i].z=z; } for(int i=1;i<=n;i++) fa[i]=i; sort(edge+1,edge+1+m,cmp); for(int i=1;i<=m;i++) { int fx=find(edge[i].x),fy=find(edge[i].y); if(fx==fy) continue; tot++;fa[fx]=fy; ans[tot]=i;sum+=edge[i].z; if(tot==n-1) break; } for(int i=1;i<=tot;i++) { k=0,num=0; for(int j=1;j<=n;j++) fa[j]=j; sort(edge+1,edge+1+m,cmp); for(int j=1;j<=m;j++) { if(j==ans[i]) continue; int fx=find(edge[j].x),fy=find(edge[j].y); if(fx!=fy) { fa[fx]=fy; num++; k+=edge[j].z; } if(num==n-1) break; } if(num==n-1&&k!=sum) answer=min(k,answer); } printf("%d",answer); }
4、最短路
floyd:http://www.cnblogs.com/z360/p/6790139.html
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 200 #define maxn 9999999 using namespace std; bool flag; int n,m,x,y,z,s,ans1,ans2,dis[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } void begin() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=(i!=j)*maxn; } void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]); } int main() { while(1) { n=read();begin(); if(n==0) break; for(int i=1;i<=n;i++) { m=read(); for(int j=1;j<=m;j++) y=read(),z=read(),dis[i][y]=z; } floyd();flag=false; ans1=0,ans2=maxn; for(int i=1;i<=n;i++) { s=0; for(int j=1;j<=n;j++) { if(i==j) continue; s=max(s,dis[i][j]); } if(s<ans2) ans1=i,ans2=s; if(s!=maxn) flag=true; } if(!flag) printf("disjoint\n"); else printf("%d %d\n",ans1,ans2); } return 0; }
spfa:http://www.cnblogs.com/z360/p/6881746.html
創建一個隊列,初始時隊列裏只有起始點,再創建一個表格記錄起始點到全部點的最短路徑(該表格的初始值要賦爲極大值,該點到他自己的路徑賦爲0)。而後執行鬆弛操做,用隊列裏有的點做爲起始點去刷新到全部點的最短路,若是刷新成功且被刷新點不在隊列中則把該點加入到隊列最後。重複執行直到隊列爲空。
說白了就是:先拿一個點(起點)入隊,而後那這個點與他相連的邊進行更新最小值,若更新成功,把相連的點加入隊列中,改點彈出,重複上訴操做,直到隊列變成空。這是咱們所要求的對短路都放在了dis數組裏。
#include<queue> #include<cstdio> #define INF 2147483647LL using namespace std; struct node { int to,dis,next; }edge[500005]; int n,m,num,head[10001],dis[10001];//n 點的個數 m 連邊的條數 s 起點 dis_1 儲存最小邊 inline void edge_add(int from,int to,int dis) { num++; edge[num].to=to; edge[num].dis=dis; edge[num].next=head[from]; head[from]=num; } void SPFA(int start) { queue<int>que; bool if_in_spfa[10001]; for(int i=1;i<=n;i++) dis[i]=INF,if_in_spfa[i]=false;//初始化 dis[start]=0,if_in_spfa[start]=true;//加入第一個點(起點) que.push(start);//將起點入隊 while(!que.empty())//若是隊列不爲空,就接着執行操做,直到隊列爲空 { int cur_1=que.front();//取出隊列的頭元素 que.pop();//將隊列頭元素彈出 for(int i=head[cur_1];i;i=edge[i].next)//枚舉與該點鏈接的邊 { if(dis[cur_1]+edge[i].dis<dis[edge[i].to])//若是能更新最小值 { dis[edge[i].to]=edge[i].dis+dis[cur_1];//更新最小值 if(!if_in_spfa[edge[i].to])//將所能更新的沒入隊的元素入隊 { if_in_spfa[edge[i].to]=true;//標記爲已入隊 que.push(edge[i].to);//推入隊中 } } } if_in_spfa[cur_1]=false;//將該點標記爲出隊列 } } int main() { int s; scanf("%d%d%d",&n,&m,&s); int from,to,dis; for(int i=1;i<=m;i++) { scanf("%d%d%d",&from,&to,&dis); edge_add(from,to,dis);//用鄰接鏈表儲存 } SPFA(s);//從起點開始spfa for(int i=1;i<=n;i++) printf("%d ",dis[i]); return 0; }
判斷有無負環:若是某個點進入隊列的次數超過N次則存在負環(SPFA沒法處理帶負環的圖)
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 200010 using namespace std; bool vis[N],vist; int n,m,t,x,y,z,tot,head[N],dis[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int to,dis,from,next; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } void begin() { tot=vist=0; memset(dis,0,sizeof(dis)); memset(head,0,sizeof(head)); memset(vis,false,sizeof(vis)); } int spfa(int s) { vis[s]=true; for(int i=head[s];i;i=edge[i].next) { int t=edge[i].to; if(dis[s]+edge[i].dis<dis[t]) { dis[t]=dis[s]+edge[i].dis; if(vis[t]||vist) { vist=true; break; } spfa(t); } } vis[s]=false; } int main() { t=read(); while(t--) { n=read(),m=read();begin(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z); if(z>=0) add(y,x,z); } for(int i=1;i<=n;i++) { spfa(i); if(vist) break; } if(vist) printf("YE5\n"); else printf("N0\n"); } return 0; }
int spfa(int x) { vis[x]=true; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+w[i]) { dis[t]=dis[x]+w[i]; if(vis[t]||spfa(t)) { vis[x]=false; return true; } } } vis[x]=false; return false; }
次短路:
先跑兩遍spfa,一遍正向,一遍逆向。而後枚舉每一條必須加入的邊,計算出加入這條邊後的路徑長度爲從前面跑到該邊的前一個節點的最短路+從該邊的後一個節點到最後的最短路+改邊的長度;判斷這條邊是否是比最短路大,且爲比最短路大的中的最小的。
#include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 510000 #define maxn 99999999 using namespace std; bool vis[N]; int n,m,x,y,z,sum,ans,tot,d1[N],d2[N],head[N]; queue<int>q; struct Edge { int to,dis,from,next; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int spfa(int s,int *dis) { for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false; vis[s]=true,dis[s]=0; q.push(s); while(!q.empty()) { int x=q.front();q.pop();vis[x]=false; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(dis[t]>dis[x]+edge[i].dis) { dis[t]=dis[x]+edge[i].dis; if(!vis[t]) vis[t]=true,q.push(t); } } } } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z),add(y,x,z); } spfa(1,d1),spfa(n,d2); ans=maxn; for(int i=1;i<=n;i++) for(int j=head[i];j;j=edge[j].next) { int t=edge[j].to; sum=d1[i]+d2[t]+edge[j].dis; if(sum>d1[n]&&sum<ans) ans=sum; } printf("%d",ans); return 0; }
5、拓撲排序
http://www.cnblogs.com/z360/p/6891705.html
拓撲排序:將有向圖中的頂點以線性方式進行排序。即對於任何鏈接自頂點u到頂點v的有向邊uv,在最後的排序結果中,頂點u老是在頂點v的前面。
精髓(具體方法):
⑴ 從圖中選擇一個入度爲0的點加入拓撲序列。
⑵ 從圖中刪除該結點以及它的全部出邊(即與之相鄰點入度減1)。
反覆執行這兩個步驟,直到全部結點都已經進入拓撲序列。
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 2000 using namespace std; priority_queue<int,vector<int>,greater<int> >q; int x,s,tot,n,m,sum,ans[N],in[N],head[N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int from,to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int main() { freopen("curriculum.in","r",stdin); freopen("curriculum.out","w",stdout); n=read(); for(int i=1;i<=n;i++) { m=read(); for(int j=1;j<=m;j++) x=read(),add(x,i),in[i]++; } for(int i=1;i<=n;i++) if(in[i]==0) q.push(i); while(!q.empty()) { x=q.top(),q.pop(); sum++;ans[++s]=x; for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; in[t]--; if(in[t]==0) q.push(t); } } if(sum!=n) printf("no\n"); else { for(int i=1;i<=s;i++) printf("%d ",ans[i]); } return 0; }
拓撲排序能夠用來判環,當咱們拓撲排序進行刪邊的時候若刪去的邊小於咱們本應有的n-1條邊,那麼就說明咱們的圖裏出現了環
1.咱們給出一系列(大小、父子、、、)關係,讓咱們將這些數進行排序,這個時候大多用拓撲排序。
2.咱們要執行一項任務,但在此以前咱們要執行完另外一些任務,求完成任務的順序,大多用拓撲排序
6、二分圖
1、匈牙利算法
http://www.cnblogs.com/z360/p/7103259.html
幾個關係:
最小頂點覆蓋是指最少的頂點數使得二分圖G中的每條邊都至少與其中一個點相關聯,二分圖的最小頂點覆蓋數=二分圖的最大匹配數;
最小路徑(邊)覆蓋是指用盡可能少的不相交簡單路徑覆蓋二分圖中的全部頂點。二分圖的最小路徑覆蓋數=|V|-二分圖的最大匹配數
最大獨立集(指尋找一個點集,使得其中任意兩點在圖中無對應邊)=|V|-二分圖的最大匹配數
講解就不粘了,有興趣的能夠去我上面的博客裏看
簡介:
設G=(V,E)是一個無向圖。如頂點集V可分割爲兩個互不相交的子集V1,V2之
選擇這樣的子集中邊數最大的子集稱爲圖的最大匹配問題,若是一個匹配中,|V1|<=|V2|且匹配數|M|=|V1|則稱此匹配爲徹底匹配,也稱做完備匹配。特別的當|V1|=|V2|稱爲完美匹配。
直接看代碼:(二分圖的最大匹配)
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1010 using namespace std; bool vis[N]; int n1,n2,m,x,y,ans,girl[N],map[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();} while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();} return x*f; } int find(int x) { for(int i=1;i<=n2;i++) if(!vis[i]&&map[x][i]) { vis[i]=true; if(!girl[i]||find(girl[i])){girl[i]=x; return 1;} } return 0; } int main() { n1=read(),n2=read(),m=read(); for(int i=1;i<=m;i++) x=read(),y=read(),map[x][y]=1; for(int i=1;i<=n1;i++) { memset(vis,0,sizeof(vis)); ans+=find(i); } printf("%d",ans); return 0; }
行列匹配:(幾種狀況)(大多都是將行當作一個集合將列當作一個集合)
1.即要行不要列。在同一行列上不能同時出現兩次某些東西,那麼就須要開兩個數組,來分別保存某些可能產生矛盾的點(所謂矛盾點就是可放的點,可是會與其餘可放的點在同一行列上而產生矛盾)的座標,即記錄行須要一個數組,列也須要一個數組。
2.當前放的點會與一些鄰近的的點產生矛盾(視題目而定,如hdu 3360)。那麼開一個數組記錄下原圖須要標號的那些點,而後根據矛盾建邊便可。
二分圖的最大權匹配:http://www.cnblogs.com/z360/p/7113231.html
二分圖題集:http://blog.csdn.net/creat2012/article/details/18094703
小總結:咱們在作題時大多數會遇到這樣兩種狀況:
1.兩個集合裏的東西是相互喜歡(能夠相互匹配)的,咱們若是要求最多能夠知足的個數,這個時候咱們大多用最大匹配來解決
2.兩個集合裏的東西實現互矛盾的(選了這個便不能選那個),咱們要求最多能夠知足的個數,這個時候咱們大多用最大獨立集
2、二分圖染色
http://www.cnblogs.com/z360/p/6954002.html
思想和步驟:咱們任意取出一個頂點進行染色,該點和相鄰的點有3種狀況:1.未染色,這樣咱們就繼續染色它的相鄰點(染爲另外一種顏色)
2.已染色 在這種狀況下咱們判斷其相鄰點染得色與該點是否相同。若不一樣,跳過該點。
3。已染色 且其相鄰點的顏色與該點的顏色相同 返回FALSE (即改圖不是二分圖)
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1001 #define maxn 9999999 using namespace std; int n,tot,sum1,sum2,head[N],a[N]; int col[N],que[N],t,h,s1[N],s2[N],f[N]; char q[N],p[N],flag; struct Edge { int from,to,next; }edge[N]; void add(int from,int to) { ++tot; edge[tot].to=to; edge[tot].next=head[from]; head[from]=tot; } void paint(int s) { queue<int> q; q.push(s); col[s]=0;//咱們把放在第一個棧中的顏色染成0 if(flag) return ; while(!q.empty()) { int x=q.front(); for(int j=head[x];j;j=edge[j].next) { if(col[edge[j].to]!=-1)//說明已被染過色 { if(col[edge[j].to]==col[x])//而且染的色與該點相同,這樣就說明其不是二分圖 flag=true; } else { col[edge[j].to]=col[x]^1;//用另外一種顏色染其相鄰的點 q.push(edge[j].to); } } q.pop(); } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); f[n+1]=maxn; for(int i=n;i>=1;i--) f[i]=min(f[i+1],a[i]);//預處理從每個點出發到序列末端的最小值,也能夠用RMQ for(int i=1;i<=n;i++)//咱們挨個判斷不能讓在同一個棧中的點,對其進行連邊,枚舉序列內起點 for(int j=i+1;j<=n;j++)//因爲咱們在這兩步中枚舉的是A數組的下標,咱們又要找不能放在一個棧中的狀況 //在前面咱們有說到:對於i<j<k&&a[K]<a[i]<a[j]的狀況是必定不能放在同一個棧中的 if(a[i]>f[j+1]&&a[i]<a[j])//在這裏咱們就是找上述的狀況 add(i,j),add(j,i);//不能放在一個棧中,連邊 memset(col,-1,sizeof(col));//初始化col數組,方便咱們判斷與一個點相連的點是否被染過色。 for(int i=1;i<=n;i++)//在這裏咱們不必定把她連成整的一棵樹,因此咱們要循環枚舉全部相連的節點,把它所有染色 if(col[i]==-1) paint(i);//染色 if(flag) {printf("0\n");return 0;}//不能用兩個棧進行排序 int now=1;//咱們要把給定的序列a排成有序的序列,而且所給的數構可構成一個1~n的排列 for(int i=1;i<=n;i++) { if(col[i]==0) { if(a[i]==now) printf("a b "),now++; else { while(s1[sum1]==now) sum1--,printf("b "),now++; s1[++sum1]=a[i],printf("a "); } } else { if(a[i]==now) printf("c d "),now++; else { while(s1[sum1]==now) sum1--,printf("b "),now++;//注意:你在這裏出棧的時候,對應的序列也已經排完了一個 s2[++sum2]=a[i],printf("c "); } } } for(int i=now;i<=n;i++) { while(i==s1[sum1]&&sum1) sum1--,printf("b "); while(i==s2[sum2]&&sum2) sum2--,printf("d "); } return 0; }
3、帶權二分圖
http://www.cnblogs.com/z360/p/7113231.html
算法流程
(1)初始化可行頂標的值
(2)用匈牙利算法尋找完備匹配
(3)若未找到完備匹配則修改可行頂標的值
(4)重複(2)(3)直到找到相等子圖的完備匹配爲止
小總結:
KM算法是用來解決最大權匹配問題的,在一個二分圖裏,左頂點爲x,右頂點爲y,現對於每組左右鏈接鏈接XiYj有權wij,求一種匹配使得全部wij的和最大。
也就是說,最大匹配必定是完美匹配。(完美匹配:二分圖的兩邊點數相同)
若是點數不相同,咱們能夠經過虛擬點權爲0的點來時的點數相同,這也就使這個圖變成了完美匹配。
#include <iostream> #include <cstring> #include <cstdio> using namespace std; const int MAXN = 305; const int INF = 0x3f3f3f3f; int love[MAXN][MAXN]; // 記錄每一個妹子和每一個男生的好感度 int ex_girl[MAXN]; // 每一個妹子的指望值 int ex_boy[MAXN]; // 每一個男生的指望值 bool vis_girl[MAXN]; // 記錄每一輪匹配匹配過的女生 bool vis_boy[MAXN]; // 記錄每一輪匹配匹配過的男生 int match[MAXN]; // 記錄每一個男生匹配到的妹子 若是沒有則爲-1 int slack[MAXN]; // 記錄每一個漢子若是能被妹子傾心最少還須要多少指望值 int N; bool dfs(int girl) { vis_girl[girl] = true; for (int boy = 0; boy < N; ++boy) { if (vis_boy[boy]) continue; // 每一輪匹配 每一個男生只嘗試一次 int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy]; if (gap == 0) { // 若是符合要求 vis_boy[boy] = true; if (match[boy] == -1 || dfs( match[boy] )) { // 找到一個沒有匹配的男生 或者該男生的妹子能夠找到其餘人 match[boy] = girl; return true; } } else { slack[boy] = min(slack[boy], gap); // slack 能夠理解爲該男生要獲得女生的傾心 還需多少指望值 取最小值 備胎的樣子【捂臉 } } return false; } int KM() { memset(match, -1, sizeof match); // 初始每一個男生都沒有匹配的女生 memset(ex_boy, 0, sizeof ex_boy); // 初始每一個男生的指望值爲0 // 每一個女生的初始指望值是與她相連的男生最大的好感度 for (int i = 0; i < N; ++i) { ex_girl[i] = love[i][0]; for (int j = 1; j < N; ++j) ex_girl[i] = max(ex_girl[i], love[i][j]); } // 嘗試爲每個女生解決歸宿問題 for (int i = 0; i < N; ++i) { fill(slack, slack + N, INF); // 由於要取最小值 初始化爲無窮大 while (1) { // 爲每一個女生解決歸宿問題的方法是 :若是找不到就下降指望值,直到找到爲止 // 記錄每輪匹配中男生女生是否被嘗試匹配過 memset(vis_girl, false, sizeof vis_girl); memset(vis_boy, false, sizeof vis_boy); if (dfs(i)) break; // 找到歸宿 退出 // 若是不能找到 就下降指望值 // 最小可下降的指望值 int d = INF; for (int j = 0; j < N; ++j) if (!vis_boy[j]) d = min(d, slack[j]); for (int j = 0; j < N; ++j) { // 全部訪問過的女生下降指望值 if (vis_girl[j]) ex_girl[j] -= d; // 全部訪問過的男生增長指望值 if (vis_boy[j]) ex_boy[j] += d; // 沒有訪問過的boy 由於girl們的指望值下降,距離獲得女生傾心又進了一步! else slack[j] -= d; } } } // 匹配完成 求出全部配對的好感度的和 int res = 0; for (int i = 0; i < N; ++i) res += love[ match[i] ][i]; return res; } int main() { while (~scanf("%d", &N)) { for (int i = 0; i < N; ++i) for (int j = 0; j < N; ++j) scanf("%d", &love[i][j]); printf("%d\n", KM()); } return 0; }
7、lca
http://www.cnblogs.com/z360/p/6822658.html
1.倍增:
倍增法就是咱們先把深度不一樣的兩個點轉化成深度相同的點。而後再對這兩個點同時倍增。
這種作法咱們先用一個數組fa[x]【y】數組來存第x個節點的2^y的父親節點。
這樣咱們就能在o(lg n)的時間內查詢任意一個點的lca。
因此咱們仍是採用上面所述的那種作法,現將深度不一樣的兩個點轉化成深度相同的兩個點。
而後再對兩個點同時進行倍增。
#include<vector> #include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 500001 #define maxn 123456 using namespace std; vector<int>vec[N]; int n,x,y,fa[N][20],deep[N],m,root; void dfs(int x) { deep[x]=deep[fa[x][0]]+1; for(int i=0;fa[x][i];i++) fa[x][i+1]=fa[fa[x][i]][i]; for(int i=0;i<vec[x].size();i++) { if(!deep[vec[x][i]]) { fa[vec[x][i]][0]=x; dfs(vec[x][i]); } } } int lca(int x,int y) { if(deep[x]>deep[y]) swap(x,y);//省下後面進行分類討論,比較方便 for(int i=18;i>=0;i--) { if(deep[fa[y][i]]>=deep[x]) y=fa[y][i];//讓一個點進行倍增,直到這兩個點的深度相同 } if(x==y) return x;//判斷兩個點在一條鏈上的狀況 for(int i=18;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { y=fa[y][i]; x=fa[x][i]; } } return fa[x][0];//這樣兩點的父親就是他們的最近公共祖先 } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } deep[root]=1; dfs(root); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
2.tarjan(離線算法、、、)
原理:Tarjan 算法創建在 DFS 的基礎上.
假如咱們正在遍歷節點 x, 那麼根據全部節點各自與 x 的 LCA 是誰, 咱們能夠將節點進行分類: x 與 x 的兄弟節點的 LCA 是 x 的父親, x 與 x 的父親的兄弟節點的 LCA 是 x 的父親的父親, x 與
x 的父親的父親的兄弟節點的 LCA 是 x 的父親的父親的父親... 將這些類別各自納入不一樣的集合中, 若是咱們可以維護好這些集合, 就可以很輕鬆地處理有關 x 節點的 LCA 的詢問. 顯然咱們可使用
並查集來維護.
咱們須要將米一組詢問用vec儲存下來,將其掛在改組詢問詢問的兩個節點上。
以後遍歷整棵樹。在訪問一個節點x時,咱們設置這個節點的fa【x】=x;只有在詢問完時,咱們將這個點的fa【x】設置城dad【x】。
以後咱們在詢問一個節點時,枚舉過於這個點的全部詢問。若果詢問中的另外一個節點已經被訪問過,那麼這兩個點的lca就是已經被訪問過的這個點。(哼,蒟蒻用tarjan作了一個題,md幾乎是調了半天,哼,不是MLE就是TLE,哼,之後不再用tarjan了!!!!)
#include<vector> #include<stdio.h> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 500001 using namespace std; vector<int>vec[N],que[N]; int n,m,qx[N],qy[N],x,y,root,fa[N],dad[N],ans[N]; int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void dfs(int x) { fa[x]=x; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=dad[x]) dad[vec[x][i]]=x,dfs(vec[x][i]); for(int i=0;i<que[x].size();i++) if(dad[y=qx[que[x][i]]^qy[que[x][i]]^x]) ans[que[x][i]]=find(y); fa[x]=dad[x]; } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } for(int i=1;i<=m;i++) { scanf("%d%d",&qx[i],&qy[i]); que[qx[i]].push_back(i); que[qy[i]].push_back(i); } dfs(root); for(int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
3.樹剖
用樹鏈剖分來求兩節點x,y的lca首先咱們要先判斷兩個節點的top那個大,咱們把top值小的節點改爲fa[top[x]]
重複上述兩個過程直到top[x]=top[y],最後將上述兩個節點中深度較小的值做爲這兩點的lca。
#include<vector> #include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 500001 #define maxn 123456 using namespace std; vector<int>vec[N]; int n,m,root,x,y,fa[N],deep[N],size[N],top[N]; int lca(int x,int y) { for( ;top[x]!=top[y];) { if(deep[top[x]]<deep[top[y]]) swap(x,y); x=fa[x]; } if(deep[x]>deep[y]) swap(x,y); return x; } void dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=0;i<vec[x].size();i++) { if(fa[x]!=vec[x][i]) { fa[vec[x][i]]=x; dfs(vec[x][i]); size[x]+=size[vec[x][i]]; } } } void dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t]) t=vec[x][i]; if(t) { top[t]=top[x]; dfs1(t); } for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&vec[x][i]!=t) dfs1(vec[x][i]); } int main() { scanf("%d%d%d",&n,&m,&root); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } dfs(root); dfs1(root); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } return 0; }
作題小方法、、、
(看lca時能夠看這個博客http://www.cnblogs.com/scau20110726/archive/2013/06/14/3135095.html)
①咱們求解如下相似問題時若數據太大,直接求lca必定超時,那麼咱們能夠用下面方法:
問題:給定一棵有n個節點的有根樹,根節點爲1,每一個節點有一個權值wi,求
即求全部無序節點對的LCA的權值之和。
解決方法:http://www.cnblogs.com/z360/p/7405849.html
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1000100 using namespace std; int n,x,y,tot; long long w[N],fa[N],sum[N],ans,head[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } struct Edge { int to,next; }edge[N<<1]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int dfs(int x) { sum[x]=1; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(to!=fa[x]) { fa[to]=x;dfs(to); ans+=(long long)sum[x]*sum[to]*(long long)w[x]; sum[x]+=(long long)sum[to]; } } } int main() { freopen("easy_LCA.in","r",stdin); freopen("easy_LCA.out","w",stdout); n=read(); for(int i=1;i<=n;i++) w[i]=read(); for(int i=1;i<n;i++) { x=read(),y=read(); add(x,y);add(y,x); } dfs(1); for(int i=1;i<=n;i++) ans+=(long long)w[i]; printf("%lld",ans); return 0; }
②咱們求解樹上的兩條路徑設爲(x,y)(xx,yy)是否有交點時,能夠分別求lca(x,y),lca(xx,yy)而後在判斷lca(x,y)的深度與lca(xx,yy)的深度,用深度深的那個lca,設爲lca1吧,咱們判斷lca1與xx或yy的lca只要爲另外一個lca那麼着兩條路徑就有交點。
具體題目:http://www.cnblogs.com/z360/p/7411760.html
#include<cstdio> #include<vector> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 1100 using namespace std; vector<int>vec[N]; int n,m,x,y,q,p,root; int fa[N],top[N],deep[N],size[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int lca(int x,int y) { for(;top[x]!=top[y];) { if(deep[top[x]]<deep[top[y]]) swap(x,y); x=fa[x]; } if(deep[x]>deep[y]) swap(x,y); return x; } int dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]) { fa[vec[x][i]]=x; dfs(vec[x][i]); size[x]+=size[vec[x][i]]; } } int dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t]) t=vec[x][i]; if(t) top[t]=top[x],dfs1(t); for(int i=0;i<vec[x].size();i++) if(vec[x][i]!=fa[x]&&vec[x][i]!=t) dfs1(vec[x][i]); } int main() { freopen("asm_virus.in","r",stdin); freopen("asm_virus.out","w",stdout); n=read(),m=read(); for(int i=1;i<n;i++) { x=read(),y=read();fa[y]=x; vec[x].push_back(y); vec[y].push_back(x); } dfs(1),dfs1(1); for(int i=1;i<=m;i++) { x=read(),y=read(),q=read(),p=read(); int Lca=lca(x,y),LCA=lca(q,p); //printf("%d %d\n",Lca,LCA); if(deep[Lca]<deep[LCA]) swap(Lca,LCA),swap(x,q),swap(y,p); if(lca(Lca,q)==Lca||lca(Lca,p)==Lca) printf("YES\n"); else printf("NO\n"); } return 0; }
③。咱們求解樹上兩個節點的最短距離時能夠經過求兩節點lca來解決,ans=dis[x]+dis[y]-2*dis[lca(x,y)]
具體題目:http://www.cnblogs.com/z360/p/7411951.html
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 51000 using namespace std; int n,m,x,y,z,t,tot,ans; int fa[N],dis[N],top[N],deep[N],size[N],head[N]; struct Edge { int from,to,dis,next; }edge[N<<1]; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]==to) continue; dis[to]=dis[x]+edge[i].dis; fa[to]=x,dfs(to);size[x]+=size[to]; } } int dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&size[t]<size[to]) t=to; } if(t) top[t]=top[x],dfs1(t); for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&to!=t) dfs1(to); } } int lca(int x,int y) { for(;top[x]!=top[y];x=fa[top[x]]) if(deep[top[x]]<deep[top[y]]) swap(x,y); if(deep[x]>deep[y]) swap(x,y); return x; } int main() { n=read(),m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); add(x,y,z),add(y,x,z); } dfs(1),dfs1(1); t=read(); for(int i=1;i<=t;i++) { x=read(),y=read(); ans=dis[x]+dis[y]-2*dis[lca(x,y)]; printf("%d\n",ans); } return 0; }
一樣,求3個點之間的最短距離課題經過求這三個點兩兩間的最短距離,相加再除以二就行了、、、
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 51000 using namespace std; int n,m,x,y,z,ans,sum,tot,ans1,ans2,ans3; int fa[N],top[N],size[N],deep[N],head[N],dis[N]; struct Edge { int to,dis,from,next; }edge[N<<1]; int add(int x,int y,int z) { tot++; edge[tot].to=y; edge[tot].dis=z; edge[tot].next=head[x]; head[x]=tot; } int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int lca(int x,int y) { for(;top[x]!=top[y];x=fa[top[x]]) if(deep[top[x]]<deep[top[y]]) swap(x,y); if(deep[x]>deep[y]) swap(x,y); return x; } int dfs(int x) { size[x]=1; deep[x]=deep[fa[x]]+1; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to) { dis[to]=dis[x]+edge[i].dis; fa[to]=x,dfs(to); size[x]+=size[to]; } } } int dfs1(int x) { int t=0; if(!top[x]) top[x]=x; for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&size[t]<size[to]) t=to; } if(t) top[t]=top[x],dfs1(t); for(int i=head[x];i;i=edge[i].next) { int to=edge[i].to; if(fa[x]!=to&&to!=t) dfs1(to); } } int begin() { ans=0;tot=0; memset(fa,0,sizeof(fa)); memset(top,0,sizeof(top)); memset(dis,0,sizeof(dis)); memset(edge,0,sizeof(edge)); memset(deep,0,sizeof(deep)); memset(size,0,sizeof(size)); memset(head,0,sizeof(head)); } int main() { while(scanf("%d",&n)!=EOF) { if(sum++) printf("\n"); begin(); for(int i=1;i<n;i++) { x=read(),y=read(),z=read(); add(x,y,z),add(y,x,z); } dfs(0),dfs1(0); m=read(); for(int i=1;i<=m;i++) { x=read(),y=read(),z=read(); ans1=dis[x]+dis[y]-2*dis[lca(x,y)]; ans2=dis[x]+dis[z]-2*dis[lca(x,z)]; ans3=dis[y]+dis[z]-2*dis[lca(y,z)]; ans=(ans1+ans2+ans3)/2; printf("%d\n",ans); } } return 0; }