拓扑排序判环

  • 拓扑排序过程
      在一个有向图中选择一个入度为零的点并且输出,从图(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();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值