图论:你好,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; }