将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。
如果存在环,那么就不可能满足u->v时u总是在v的前面了。所以必须是有向五环图(DAG)才能拓扑排序。
如果该DAG任意两个顶点都有确定的关系,拓扑排序就是唯一的。如果有这么一个唯一的拓扑排序,容易知道这样的顺序恰好能够遍历全图且每个顶点只经过一次,我们把这样的路径叫做哈密顿路径。
维护一个入度为0的集合S,每次从S中取出一个点放入存储答案的L数组,检查该点出发的所有边及连接的点,从图中移除这些边,如果连接的点在删除这条边以后入度变为0,将它加入到集合S中。 如此循环,直到集合S中没有点为止,检查图上还有没有边存在,如果有就存在环,如果没有L数组中存储的就是拓扑排序的结果。 集合S可以用栈实现。
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <vector> using namespace std; int in[100100],n,m; priority_queue<int> s; vector<vector<int> > edge(100010); vector<int> ans; void Kahn(){ while(!s.empty()){ int cur=s.top(); s.pop(); ans.push_back(cur); printf("%d ",cur); vector<int> :: iterator it; for(it=edge[cur].begin();it!=edge[cur].end();it++){ in[*it]--; if(!in[*it]){ s.push(*it); } } } } int main(){ memset(in,0,sizeof(in)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); edge[x].push_back(y); in[y]++; } for(int i=1;i<=m;i++){ if(in[i]==0) { s.push(i); //printf("%d ",i); } } Kahn(); return 0; }编程输入3个整数n,p,q,寻找一个由整数组成的数列(a1,a2,……,an),要求:其中任意连续p项之和为正数,任意连续q项之和为负数。0
仅一行分别表示n,p,q,之间用一个空格隔开。
只有一行,有解即输出这个数列,每个数之间用一个空格隔开。否则输出NO。
用数组S[i]代表前缀和 那么第k位数到第k+p-1位数的和就是S[k+p-1]-S[k-1]>0,可得S[k+p-1]>S[k-1] 同理S[k+q-1]-S[k-1]<0->S[k-1]>S[k+q-1] 如果S[j]>s[k]就从j->k连一条边,拓扑排序若有环则无解,否则拓扑序就是答案。
题解戳这里
#include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <vector> using namespace std; int in[100100],n,m,clock,ou[100100]; priority_queue<int> s; vector<vector<int> > edge(100010); vector<int> ans; void Kahn(){ while(!s.empty()){ int cur=s.top(); s.pop(); ans.push_back(cur); ou[cur]=clock--; //printf("%d ",cur); for(int i=0;i<edge[cur].size();i++){//vector遍历范式 in[edge[cur][i]]--; if(!in[edge[cur][i]]){ s.push(edge[cur][i]); } } } } int main(){ memset(in,0,sizeof(in)); scanf("%d%d",&n,&m); clock=n; for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); edge[y].push_back(x); in[x]++; } for(int i=1;i<=n;i++){ if(in[i]==0) { s.push(i); //printf("%d ",i); } } Kahn(); for(int i=1;i<=n;i++) printf("%d ",ou[i]); return 0; }维护一个点集P{}存放已经加入生成树的点,维护一个边集E1{}【使用优先队列,以边权排序】存放所有与点集P中的点相连而没有加入生成树的边,维护边集E2{}存放已经加入生成树的边。 算法流程如下:
随意选一个顶点u加入P,以P为起点更新E1E1.top()取出边(u,v):如果v还没有被访问过,把该边放入E2,将v加入P,E1.pop();否则直接E1.pop(),返回该步骤的起点。以v为起点更新E1,返回第二步循环,直至所有顶点都加入了P为止(计数达到顶点数就停止循环)这里要转个弯,因为两种最小生成树算法(Prim&Kruskal)都是贪心算法,每次都在选取边权最小的边加入生成树,所以天然满足“最大边权最小”的条件。【证明:如果所求解不是最小生成树,必然会有一条边的边权与最小生成树不同,如果该边权比最小生成树中的小,在选择加入生成树时就会选它而不是边权更大的边,证毕。】然后求解最小生成树即可。
#include <cstdio> #include <algorithm> #include <cstring> #define maxn 300+20 using namespace std; struct edge{ int l,r,w; }; bool operator <(const edge &a,const edge &b){ return a.w<b.w; } int fa[maxn],n,m,u,v,c,ans=0; edge g[20100]; int find(int a){ return fa[a] == a ? fa[a] : fa[a]=find(fa[a]); } void Kruskal(){ int cnt=0; sort(g+1,g+m+1); for(int i=1,j=0;i<=m,j<n-1;i++){ int a=g[i].l; int b=g[i].r; if(find(a)!=find(b)){ ans=g[i].w; fa[find(a)]=find(b); j++; } } return; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ scanf("%d%d%d",&u,&v,&c); g[i].l=u; g[i].r=v; g[i].w=c; } for(int i=1;i<=n;i++) fa[i]=i; Kruskal(); printf("%d %d",n-1,ans); }Prim的时间复杂度为O(n^2),n为图中顶点的个数,与边无关。所以近乎完全图的问题用Prim的效率更高,下面学习适用于稀疏图的复杂度为O(ElogE)的Kruskal算法。
将边(u,v)快排,从小到大考察加入生成树,如果u和v还没有相连(不属于同一个连通分量)就将这条边选入生成树,并将u和v所在联通分量合并,直到加入的边数为n-1为止。
关键点:边快排+并查集判断连通分量
题目条件告诉我们,我们要把这个图分成K个子图,这些子图中的边权之和最小。用Kruskal添边时会有一个合并并查集的操作——每添一条边集合就会少一个,所以添N-K条边即可。
#include <cstdio> #include <algorithm> #include <cstring> #define maxn 20000 using namespace std; struct edge{ int l,r,w; }; bool operator <(const edge &a,const edge &b){ return a.w<b.w; } int fa[maxn],n,m,u,v,c,k,ans=0; bool flag=false; edge g[20000]; int find(int a){ return fa[a] == a ? fa[a] : fa[a]=find(fa[a]); } void Kruskal(){ int j=0; sort(g+1,g+m+1); for(int i=1;i<=m;i++){ int a=g[i].l; int b=g[i].r; if(find(a)!=find(b)){ ans+=g[i].w; fa[find(a)]=find(b); j++; } if(j>=n-k) { flag=true;break; } } } int main(){ scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++){ scanf("%d%d%d",&u,&v,&c); g[i].l=u; g[i].r=v; g[i].w=c; } for(int i=1;i<=n;i++) fa[i]=i; Kruskal(); if(flag) printf("%d",ans); else printf("No Answer"); return 0; }只有当图不存在负权回路时才有单源最短路
如果图中存在负权回路,就不可能有最短路了(越走越小…),用SPFA算法统计入队次数可以很容易判断图中是否有负权回路。方法有两种:
总的点入队次数大于点数的两倍单个点的入队次数大于sqrt(总的点数)转载于:https://www.cnblogs.com/leotan0321/p/6081365.html
相关资源:各显卡算力对照表!