- 拓扑排序过程
在一个有向图中选择一个入度为零的点并且输出,从图(vector建图)中删除这个点和所有以它为尾的边,一直重复上述过程直到不存在没有前驱的顶点。如果此时输出的顶点数小于有向图中的顶点数,说明图中存在环,否则输出的顶点序列是拓扑序列。
//二分+拓扑排序(离线计算)
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#define pi 3.1415926
#define mod 1000000007
using namespace std;
//typedef pair<int,int> Node;
typedef long long LL;
const int Max_n=100005;
int n,m;
int in[Max_n];//每个点的入度
struct Edge{
int s,e;
}edge[Max_n<<1];//m<2*10^5
int check(int mid){//拓扑排序判环
memset(in,0,sizeof(in));
vector<int>v[Max_n];
for(int i=1;i<=mid;i++){//mid及mid之前的数据是否存在环
v[edge[i].s].push_back(edge[i].e);//建图(类似与邻接表)
in[edge[i].e]++;
}
int sum=0;//拓扑排序所有能够出来的点的总数
queue<int>q;
for(int i=1;i<=n;i++)//如果有入度为0的点,则加入到队列中
if(!in[i]) q.push(i);
while(q.size()){
int x=q.front();q.pop();//入度为0的点出队
sum++;//总数+1
for(unsigned int i=0;i<v[x].size();i++){//注意这里从0开始
//所有与x相连接的点入度-1
in[v[x][i]]--;
if(in[v[x][i]]==0) q.push(v[x][i]);
}
}
return sum==n;//出来的所有点的总数为n说明不存在环,否则存在环
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&edge[i].s,&edge[i].e);
int l=1,r=m,ans;
while(l<=r){
int mid=l+(r-l)/2;
if(check(mid)){//此时中点前面不存在环,存在环的点在右边
l=mid+1;
ans=mid;//记录不存在环的最后一个点
}else{//中点之前存在环
r=mid-1;
}
}
for(int i=1;i<=ans;i++) printf("Yes\n");
for(int i=ans+1;i<=m;i++) printf("No\n");
return 0;
}
hdu1285:确定比赛名次
theme:n只队伍比赛,给定m次比赛的结果:p1 p2表示队伍p1赢了p2,请将n只队伍按排名先后输出,如果有多种答案,则先输出编号最小的。
solution:赢了的队伍肯定先输出,所以按p1->p2建图,由于最后输出时位于同一级时先输出编号小的,所以用优先队列模拟最小堆,将度为0的编号按从小到大输出即可。
#include<bits/stdc++.h>
using namespace std;
#define pb(x) push_back(x)
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
struct _a
{
int p1,p2;
}a[550];
int in[550];
int n,m;
void check()
{
memset(in,0,sizeof(in));
vector<int>v[550];
far(i,0,m)//p2<p1
v[a[i].p1].push_back(a[i].p2),in[a[i].p2]++;
priority_queue<int,vector<int>,greater<int>>q;
far(i,1,n+1)
if(in[i]==0)
q.push(i);
int flag=0;
while(!q.empty())
{
int u=q.top();
q.pop();
if(flag==0)
printf("%d",u),flag=1;
else
printf(" %d",u);
int s=v[u].size();
far(i,0,s)
{
int t=v[u][i];
in[t]--;
if(in[t]==0)
q.push(t);
}
}
puts("");
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
far(i,0,m)
scanf("%d%d",&a[i].p1,&a[i].p2);
check();
}
}
D. Gourmet choice
theme:给定两堆物品,第一堆有n个,第二堆有m个,现给定n*m的矩阵,矩阵仅由>、<、=组成,表示第一堆的第i个物品的价值>/</=第二堆的第j个物品。现让你用最少的数字给这n+m个物品编号,使得序号满足矩阵中的大小关系。
solution:如果没有=的话,那就是裸的拓扑排序,有=的话,我们可以先用并查集将价值相等的物品归为一个节点,再使用拓扑排序按度为0的顺序编号。
#include<bits/stdc++.h>
using namespace std;
#define pb(x) push_back(x)
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int fa[3000];
int Find(int x)
{
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
void unit(int x,int y)
{
x=Find(x);
y=Find(y);
fa[y]=x;
}
char a[1010][1010];
int ans[3010];
int vis[3030];
int in[3000];
int n,m;
void check(){//拓扑排序
memset(in,0,sizeof(in));
memset(ans,0,sizeof(ans));
memset(vis,0,sizeof(vis));
vector<int>v[3000];
far(i,1,n+1)//建图
far(j,1,m+1)
{
if(a[i][j]=='>')
v[Find(j+n)].push_back(Find(i)),in[Find(i)]++;
else if(a[i][j]=='<')
v[Find(i)].push_back(Find(j+n)),in[Find(j+n)]++;
}
queue<int>q;
for(int i=1;i<=n+m;i++)//如果有入度为0的点,则加入到队列中
if(!in[Find(i)])
{
int y=Find(i);
if(!vis[y])//注意这里的判断,否则会重复加入
{
q.push(y);
ans[y]=1;
vis[y]=1;
}
}
while(q.size()){
int x=q.front();q.pop();//入度为0的点出队
x=Find(x);
for(unsigned int i=0;i<v[x].size();i++){//注意这里从0开始
//所有与x相连接的点入度-1
int y=Find(v[x][i]);
in[y]--;
if(in[y]==0)
{
q.push(y);
ans[y]=ans[x]+1;
vis[y]=1;
}
}
}
far(i,1,n+m+1)//说明存在环,该节点不可能度为0
if(!vis[Find(i)])
{
puts("No");
return;
}
puts("Yes");
far(i,1,n+1)
printf("%d ",ans[Find(i)]);
puts("");
far(i,1,m+1)
printf("%d ",ans[Find(n+i)]);
puts("");
}
int main()
{
scanf("%d%d",&n,&m);
far(i,1,n+1)
scanf("%s",a[i]+1);
far(i,1,n+m+1)
fa[i]=i;
far(i,1,n+1)
far(j,1,m+1)
if(a[i][j]=='=')
unit(i,j+n);
check();
}