学离散的偏序关系时的一个应用,
问题:小方准备组织一次演讲比赛,有如下环节(未排序):
- 进行彩排
- 确定演讲主题
- 租借服装
- 确定比赛时间
- 确定比赛地点
- 确定参赛人员
- 申请经费
- 确定嘉宾人选
- 购买奖品,装饰物
- 比赛啦~
但是:
- 确定演讲主题之后才能确定比赛时间;
- 确定主题之后才能准备申请经费
- 在确定选手是否能参加之前先要确定比赛时间
- 购买何种装饰物需由比赛地点确定
- 只有当选手确定之后,才能去借服装,租场地和确定要邀请的嘉宾
- 如果申请经费没有成功,那么不能去购买奖品和装饰品
- 在彩排之前需要借服装和确定地点
- 比赛只有当嘉宾都邀请成功,已购买奖品而且彩排过才能进行
这些环节之间有一些约束关系,那么该怎么办呢;
画偏序关系的哈斯图形式(方案可能有多种):
{
偏序关系:(自反性,反对称性,传递性)
哈斯图的画法:
1,删除自环;
2,具有传递性的,删掉可直达的
3,重组位置,使得有向的箭头指向正上方或斜上方。
}
拓扑排序(topologicalSorting):
输入:偏序集(A,R)
输出:A的元素在<意义下“从小到大”逐一列出所形成的序列
1, B<- A,S<-R,List<-空
2, while|B|>0 do
2.1, 从B中选择一个极小元x加入队列list
2.2, B<- B- {x}
2.3, S<- S|B
3, return list
维基百科上关于Kahn算法的伪码描述:
L← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
remove a node n from S
insert n into L
foreach node m with an edge e from nto m do
remove edge e from the graph
if m has no other incoming edges then
insert m into S
if graph has edges then
return error (graph has at least onecycle)
else
return L (a topologically sortedorder)
算法:(kahn)
对于一个有向无环图(DAG)来说:
1), 统计所有节点的入度,对于入度为0的节点就可以分离出来,然后将这个节点指向所有节点的入度减去1;
2), 重复1),知道所有的节点都被分离出来,拓扑排序结束。
3), 如果最后不存在入度为0的节点,那就说明图有环,无解。
注:对于入度为0的节点,则这个节点没有前驱,只要不是孤立节点,那么完成这个节点之后,对于它的所有后继节点来说,前驱结点就完成了一个,入度进行-1。
代码:(kahn)
bool toposort() { q = new queue(); for (i = 0; i < n; i++) if (in_deg[i] == 0) q.push(i); ans = new vector(); while (!q.empty()) { u = q.pop(); ans.push_back(u); for each edge(u, v) { if (--in_deg[v] == 0) q.push(v); } } if (ans.size() == n) { for (i = 0; i < n; i++) std::cout << ans[i] << std::endl; return true; } else { return false; } }
拓扑排序的阶过不唯一,所以一般会按某种要求进行输出,就需要用到优先队列,按字典序输出:
1 vector<int> head[maxn],ans; 2 int n,m,in[maxn]; 3 void topologicalsorting() 4 { 5 cin>>n>>m; 6 for(int i=0; i<m; i++) 7 { 8 int u,v; 9 scanf("%d%d",&u,&v); 10 head[u].push_back(v); 11 in[v]++; 12 } 13 priority_queue<int,vector<int>,greater<int> > q; 14 for(int i=1; i<=n; i++) 15 { 16 if(!in[i]) 17 { 18 q.push(i); 19 } 20 } 21 while(q.empty()&&ans.size()<n) 22 { 23 int v=q.top(); 24 q.pop(); 25 ans.push_back(v); 26 for(int i=0; i<head[v].size(); i++) 27 { 28 in[head[v][i]]--; 29 if(!in[head[v][i]]) 30 q.push(head[v][i]); 31 } 32 } 33 if(ans.size()==n) 34 { 35 //找到了排序 36 } 37 else 38 { 39 //图里有自环; 40 } 41 42 }
算法:(DFS)
DFS版本:
1 // dfs 版本 2 bool dfs(int u) { 3 c[u] = -1; 4 for (int v = 0; v <= n; v++) 5 if (G[u][v]) { 6 if (c[v] < 0) 7 return false; 8 else if (!c[v]) 9 dfs(v); 10 } 11 c[u] = 1; 12 topo.push_back(u); 13 return true; 14 } 15 bool toposort() { 16 topo.clear(); 17 memset(c, 0, sizeof(c)); 18 for (int u = 0; u <= n; u++) 19 if (!c[u]) 20 if (!dfs(u)) return false; 21 reverse(topo.begin(), topo.end()); 22 return true; 23 }
考虑一个图,删掉某个入度为 的节点之后,如果新图可以拓扑排序,那么原图一定也可以。反过来,如果原图可以拓扑排序,那么删掉后也可以。
练习:
题的类型:
正向:
1,判断自环问题,
2,输出拓扑排序
3,字典序输出拓扑排序(最小优先队列维护)
反向拓扑:(最大优先队列维护)
1,自己是一个编号的
2,给自己贴标签问题
1,判断自环问题:链接
题意:就裸拓扑排序,判断一下自环;
code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1e5+10; 4 vector<int> G[maxn]; 5 int in[maxn]; 6 int n,m; 7 int sum; 8 queue<int> q; 9 int toposort() 10 { 11 while(!q.empty()) 12 q.pop(); 13 sum=0; 14 for(int i=1; i<=n; i++) 15 if(!in[i]) 16 q.push(i); 17 while(!q.empty()) 18 { 19 int v=q.front(); 20 q.pop(); 21 sum++; 22 for(int i=0; i<G[v].size(); i++) 23 { 24 in[G[v][i]]--; 25 if(!in[G[v][i]]) 26 q.push(G[v][i]); 27 } 28 } 29 if(sum==n) 30 return 1; 31 else 32 { 33 return 0; 34 } 35 36 } 37 int main() 38 { 39 int t; 40 cin>>t; 41 while(t--) 42 { 43 cin>>n>>m; 44 memset(G,0,sizeof(G)); 45 memset(in,0,sizeof(in)); 46 for(int i=1; i<=m; i++) 47 { 48 int u,v; 49 cin>>u>>v; 50 G[u].push_back(v); 51 in[v]++; 52 } 53 if(toposort()) 54 cout<<"Correct"<<endl; 55 else 56 { 57 cout<<"Wrong"<<endl; 58 } 59 60 } 61 //system("pause"); 62 return 0; 63 }
2,同1,判断自环,注意范围变了,从0~n-1;(HDU3342)
code:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1e5+10; 4 vector<int> G[maxn]; 5 int in[maxn]; 6 int n,m; 7 int sum; 8 queue<int> q; 9 int toposort() 10 { 11 while(!q.empty()) 12 q.pop(); 13 sum=0; 14 for(int i=0; i<n; i++) 15 if(!in[i]) 16 q.push(i); 17 while(!q.empty()) 18 { 19 int v=q.front(); 20 q.pop(); 21 sum++; 22 for(int i=0; i<G[v].size(); i++) 23 { 24 in[G[v][i]]--; 25 if(!in[G[v][i]]) 26 q.push(G[v][i]); 27 } 28 } 29 if(sum==n) 30 return 1; 31 else 32 { 33 return 0; 34 } 35 36 } 37 int main() 38 { 39 40 while(cin>>n>>m) 41 { 42 if(!n&&!m) 43 break; 44 memset(G,0,sizeof(G)); 45 memset(in,0,sizeof(in)); 46 for(int i=1; i<=m; i++) 47 { 48 int u,v; 49 cin>>u>>v; 50 G[u].push_back(v); 51 in[v]++; 52 } 53 if(toposort()) 54 cout<<"YES"<<endl; 55 else 56 { 57 cout<<"NO"<<endl; 58 } 59 } 60 system("pause"); 61 return 0; 62 }
3,拓扑排序的应用;链接
题意:有n个节点,初始时,第k个节点上有一个病毒,病毒可以传递,问最终病毒的总和
题解:拓扑排序裸题,路过每个结点时加一下,即可(它的后继的病毒数,就等于后继本来的病毒数+它的病毒数,遍历完就可以)
code;
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e5+10; 4 const int mod=142857; 5 vector<int> G[maxn]; 6 int in[maxn]; 7 int a[maxn]; 8 queue<int> q; 9 int n,m,k; 10 void toposort() 11 { 12 while(!q.empty()) q.pop(); 13 for(int i=1; i<=n; i++) 14 if(!in[i]) 15 q.push(i); 16 while(!q.empty()) 17 { 18 int v=q.front(); 19 q.pop(); 20 for(int i=0; i<G[v].size(); i++) 21 { 22 in[G[v][i]]--; 23 if(!in[G[v][i]]) 24 q.push(G[v][i]); 25 a[G[v][i]]=(a[G[v][i]]+a[v])%mod; 26 } 27 } 28 } 29 int main() 30 { 31 while(cin>>n>>m>>k) 32 { 33 int x; 34 for(int i=1; i<=k; i++) 35 { 36 cin>>x; 37 a[x]++; 38 } 39 for(int i=1; i<=m; i++) 40 { 41 int u,v; 42 cin>>u>>v; 43 G[u].push_back(v); 44 in[v]++; 45 } 46 toposort(); 47 int sum=0; 48 for(int i=1; i<=n; i++) 49 sum=(sum+a[i])%mod; 50 cout<<sum<<endl; 51 } 52 }
4,拓扑排序+dp,skiing
题意:给你n个点,m条边的DAG,求一条最长路
题解:首先拓扑排序的中心思想不变,然后考虑一下最长路,思想就最短路的思想,dp[v]表示在从u(入度为0的点)开始到节点v的最长路径,
然后状态转移方程dp[v]=max(dp[v],dp[u]+w(u,v) )(最短路思想,想一下)最后max(dp[v])就是答案;
code:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; const int mod=142857; vector<pair<int,int> > G[maxn]; int in[maxn]; int dp[maxn]; int n,m; void toposort() { queue<int> q; while(!q.empty()) q.pop(); for(int i=1; i<=n; i++) if(!in[i]) q.push(i); while(!q.empty()) { int v=q.front(); q.pop(); for(int i=0; i<G[v].size(); i++) { int edge=G[v][i].first; in[edge]--; if(!in[edge]) q.push(edge); dp[edge]=max(dp[edge],dp[v]+G[v][i].second); } } } int main() { int t; scanf("%d",&t); while(t--) { //int n,m; cin>>n>>m; memset(in,0,sizeof(in)); memset(dp,0,sizeof(dp)); for(int i=1; i<=n; i++) G[i].clear(); for(int i=1; i<=m; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w ); G[u].push_back(make_pair(v,w)); in[v]++; } toposort(); int ma=0; for(int i=1; i<=n; i++) ma=max(ma,dp[i]); cout<<ma<<endl; } return 0; }
5,Almost Acyclic Graph 链接
题意:给一张图,可以删除一条边,问图可否构成没有自环的图
裸拓扑排序,对入度进行减一,判断一下即可;
code;
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn=1e5+10; 4 int in[maxn],deg[maxn]; 5 vector<int> G[maxn]; 6 int n,m; 7 queue<int> q; 8 int toposort() 9 { 10 while(!q.empty()) q.pop(); 11 for(int i=1; i<=n; i++) 12 if(!in[i]) 13 q.push(i); 14 int sum=0; 15 while(!q.empty()) 16 { 17 int v=q.front(); 18 q.pop(); 19 sum++; 20 for(int i=0; i<G[v].size(); i++) 21 { 22 in[G[v][i]]--; 23 if(!in[G[v][i]]) 24 q.push(G[v][i]); 25 } 26 } 27 if(sum==n) 28 return 1; 29 else 30 return 0; 31 } 32 int main() 33 { 34 cin>>n>>m; 35 while(m--) 36 { 37 int u,v; 38 cin>>u>>v; 39 G[u].push_back(v); 40 in[v]++; 41 deg[v]++; 42 } 43 if(toposort()) cout<<"YES "; 44 else 45 { 46 int flag=0; 47 for(int i=1; i<=n; i++) 48 { 49 if(deg[i]>=1) 50 { 51 memcpy(in,deg,sizeof deg); 52 in[i]--; 53 if(toposort()) 54 { 55 flag=1; 56 cout<<"YES "; 57 break; 58 } 59 } 60 } 61 if(!flag) cout<<"NO "; 62 } 63 }
裸拓扑排序,
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; vector<int> G[maxn],ans; int in[maxn]; int n; queue<int> q; void toposort() { for(int i=1; i<=n; i++) if(!in[i]) q.push(i); while(!q.empty()) { int v=q.front(); q.pop(); ans.push_back(v); for(int i=0; i<G[v].size(); i++) { in[G[v][i]]--; if(!in[G[v][i]]) q.push(G[v][i]); } } for(auto x:ans) cout<<x<<" "; } int main() { cin>>n; for(int i=1; i<=n; i++) { int x; while(cin>>x) { if(!x) break; G[i].push_back(x); in[x]++; } } toposort(); }
7,确定比赛名次
回到刚开始的板子,拓扑排序+优先队列字典序输出
这个题要小的先输出,还是裸的拓扑排序
code:
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10; int in[maxn],deg[maxn]; vector<int> G[maxn]; int n,m; priority_queue<int,vector<int>,greater<int> > q; void toposort() { while(!q.empty()) q.pop(); vector<int> ans; for(int i=1; i<=n; i++) if(!in[i]) q.push(i); while(!q.empty()) { int v=q.top(); q.pop(); ans.push_back(v); for(int i=0; i<G[v].size(); i++) { in[G[v][i]]--; if(!in[G[v][i]]) q.push(G[v][i]); } } for(int i=0; i<ans.size(); i++) if(i!=n-1) printf("%d ",ans[i]); else printf("%d ",ans[i]); } int main() { while(cin>>n>>m) { for(int i=0; i<=n; i++) { G[i].clear(); } memset(in,0,sizeof(in)); while(m--) { int u,v; cin>>u>>v; G[u].push_back(v); in[v]++; } toposort(); } }
观察可以看出按上一个题的写法不对,那么考虑一下反向建边,然后跑一遍字典序最大的拓扑排序优先队列维护,然后倒序输出就可
code:
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+10; int in[maxn],deg[maxn]; vector<int> G[maxn]; int n,m; priority_queue<int> q; void toposort() { while(!q.empty()) q.pop(); vector<int> ans; for(int i=1; i<=n; i++) if(!in[i]) q.push(i); while(!q.empty()) { int v=q.top(); q.pop(); ans.push_back(v); for(int i=0; i<G[v].size(); i++) { in[G[v][i]]--; if(!in[G[v][i]]) q.push(G[v][i]); } } for(int i=n-1; i>=0; i--) if(i!=0) printf("%d ",ans[i]); else printf("%d ",ans[i]); } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) { G[i].clear(); } memset(in,0,sizeof(in)); while(m--) { int u,v; scanf("%d%d",&u,&v); G[v].push_back(u); in[u]++; } toposort(); } }
9,poj3678Labeling Balls
优先队列+反向拓扑
根据题的要求,要先保证1号球最轻,若我们由轻的向重的连边,然后我们依次有小到大每次把重量分给一个入度为0的点,那么在拓扑时我们面对多个入度为0的点,我们不知道该把最轻的分给谁才能以最快的速度找到1号(使1号入度为0),并把当前最轻的分给1号。所以我们要由重的向轻的连边,然后从大到小每次把一个重量分给一个入度为0的点。这样我们就不用急于探求最小号。我们只需要一直给最大号附最大值,尽量不给小号赋值,这样自然而然就会把轻的重量留给小号。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #include<cstring> using namespace std; const int maxn=1e5+10; typedef long long ll; std::vector<int> G[maxn]; int ans[maxn]; int n,m; int in[maxn]; priority_queue<int> q; void toposort() { int sum=0; int cnt=n; while(!q.empty()) q.pop(); for(int i=1; i<=n; i++) if(!in[i]) q.push(i); while(!q.empty()) { int v=q.top(); q.pop(); ans[v]=cnt--; sum++; for(int i=0; i<G[v].size(); i++) { in[G[v][i]]--; if(!in[G[v][i]]) q.push(G[v][i]); } } if(sum==n) { for(int i=1; i<=n; i++) if(i==n) printf("%d ",ans[i]); else printf("%d ",ans[i]); } else printf("-1 "); } void solve() { cin>>n>>m; memset(in,0,sizeof(in)); memset(ans,0,sizeof(ans)); for(int i=1; i<=n; i++) G[i].clear(); while(m--) { int u,v; cin>>u>>v; G[v].push_back(u); in[u]++; } toposort(); } int main() { int t; cin>>t; while(t--) solve(); return 0; }