拓扑排序初学
拓扑排序:让所有的有向边全都从左指向右,同时将所有顶点排列在一条水平线上(可能有很多种排序顺序,不一定唯一)
1.简单理解
个人理解:可以理解为扒皮,先扒最外层,也就是h[]=0,然后与之相连的点都会h–,
即往外一层,以此类推就会全部扒完。如果存在环的话,就没有拓扑排序,可以通过比较ans.size()和n的关系来判断是否能形成拓扑排序
这是我最常用的板子,感觉是最好理解的
注意多组输入的时候一定要把edge数组清空,还有ans等等
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];//用来表示连接情况
queue<int> q;
int main()
{
int n,m,a,b;
cin>>n>>m;
for (int i=0; i<m; i++)
{
cin>>a>>b;
edge[a].pb(b);
h[b]++;
}
for (int i=0; i<n; i++)
{
if (h[i]==0)
q.push(i);
}
vector<int > ans;
while(!q.empty())
{
int p=q.front();
q.pop();
ans.pb(p);
for (int i=0; i<edge[p].size(); i++)
{
int y=edge[p][i];
h[y]--;
if (h[y]==0)
q.push(y);
}
}
for (int i=0; i<ans.size(); i++)
cout<<ans[i]<<endl;
}
2.拓展应用
例1:HDU 1285 http://acm.hdu.edu.cn/showproblem.php?pid=1285
拓扑排序+优先队列维护
题目大意:一共N个队伍,给出M对数P1,P2,表示P1会战胜P2,最后输出拓扑排序,如果有很多组情况,就尽量把队伍小的放前面
那么就很显然直接把队列改成优先队列(小的优先)就可以了,这样每次取下一层的时候都会优先选择队伍小的了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];
priority_queue<int,vector<int >,greater<int> > q;
vector<int > ans;
int main()
{
int n,m,a,b;
while(~scanf("%d%d",&n,&m))
{
for (int i=1; i<=n; i++)
{
edge[i].clear();
h[i]=0;
}
ans.clear();
while(!q.empty())
{
q.pop();
}
for (int i=0; i<m; i++)
{
scanf("%d%d",&a,&b);
edge[a].pb(b);
h[b]++;
}
for (int i=1; i<=n; i++)
{
if (h[i]==0)
q.push(i);
}
while(!q.empty())
{
int p=q.top();
q.pop();
ans.pb(p);
for (int i=0; i<edge[p].size(); i++)
{
int y=edge[p][i];
h[y]--;
if (h[y]==0)
q.push(y);
}
}
//cout<<ans.size()<<endl;
for (int i=0; i<ans.size(); i++)
{
cout<<ans[i];
if (i!=ans.size()-1)
cout<<" ";
}
cout<<endl;
}
}
例2:HDU 4857 http://acm.hdu.edu.cn/showproblem.php?pid=4857
反向建图+拓扑排序+优先队列
题目大意:给出M个条件P1,P2,要求P1比P2更早逃生,同时要保证编号小的尽量靠前(1的位置在前,如果1的位置相同,2的位置在前,以此类推),即如果6-1-2-3-4-5和2-3-1-4-5-6这两个排序优先选择前者
坑点:假如我现在给出的数据是5优先于4优先于2,6优先于1优先于3 。如果正向思路去想,肯定是用一个优先队列(小的优先)去维护,但是发现维护到最后的顺序是5-4-2-6-1-3,而更优的则是6-1-5-4-2-3 即说明一个问题,正向建图会忽视后面的数字。
换个角度想,如果小的要尽量在前,那么大的肯定尽量在后面,如果我反向建图,并求一个反向的拓扑序的话,肯定是符合条件的。(虽然不会证明
/*为什么会出现这个结果,我迷迷糊糊口胡两句,
如果从正向建图优先队列找到了当前最外层最小的数字,
但是这个数字比如说是4,我还是要去尽量保证1或者2或者3是最前的,
所以相当于和大局没有任何关系,白费功夫。*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=100009;
int h[maxn];
vector<int > edge[maxn];
priority_queue<int> q;
vector<int > ans;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m,a,b;
scanf("%d%d",&n,&m);
for (int i=0; i<n; i++)
{
edge[i].clear();
h[i]=0;
}
ans.clear();
while(!q.empty())
{
q.pop();
}
for (int i=0; i<m; i++)
{
scanf("%d%d",&a,&b);
a--;
b--;
edge[b].pb(a);
h[a]++;
}
for (int i=0; i<n; i++)
{
if (h[i]==0)
q.push(i);
}
while(!q.empty())
{
int p=q.top();
q.pop();
ans.pb(p);
for (int i=0; i<edge[p].size(); i++)
{
int y=edge[p][i];
h[y]--;
if (h[y]==0)
q.push(y);
}
}
//cout<<ans.size()<<endl;
for (int i=ans.size()-1; i>=0; i--)
{
cout<<ans[i]+1;
if (i!=0)cout<<" ";
}
cout<<endl;
}
return 0;
}
例3:HDU 2647 http://acm.hdu.edu.cn/showproblem.php?pid=2647
题目大意:发工资每人最少888,一共N个人,给出M组条件,要求a比b工资高,求最少工资
(这题其实不用拓扑序也可以做。。
需要求每一个人的add[i],表示比888多多少钱
比如说一共五个人,会出现这种情况,需要考虑到
我们可以看出,一个人的add[x]是和他相连的所有人的(add[y]+1)取max,不难理解
那么我们就跑一遍拓扑排序,在找邻边的过程中不断修改add[]值就可以了
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<set>
#include<stdlib.h>
#define ll long long
#define pb push_back
using namespace std;
const int maxn=10009;
vector<int >E[maxn];
vector<int >ans;
int h[maxn];
int add[maxn];
queue<int > q;
int main()
{
int n,m,a,b;
while(~scanf("%d%d",&n,&m))
{
for (int i=1; i<=n; i++)
{
h[i]=0;
E[i].clear();
}
while(!q.empty()){q.pop();}
ans.clear();
memset(add,0,sizeof add);
for (int i=0; i<m; i++)
{
scanf("%d%d",&a,&b);
E[b].pb(a);
h[a]++;
}
for (int i=1; i<=n; i++)
{
if (h[i]==0)
q.push(i);
}
while(!q.empty())
{
int now=q.front();q.pop();
ans.pb(now);
for (int i=0; i<E[now].size(); i++)
{
int y=E[now][i];
h[y]--;
if (h[y]==0)
{
add[y]=max(add[y],add[now]+1);
q.push(y);
}
}
}
if (ans.size()!=n)
cout<<-1<<endl;
else
{
int cnt=0;
for (int i=1; i<=n; i++)
cnt+=add[i];
cout<<cnt+n*888<<endl;
}
}
}
/*5 5
5 1
3 1
4 3
2 4
2 5*/