PTA---7-11 关键活动(拓扑排序(DFS判断回路)+关键路径)

PTA—7-11 关键活动

直达链接点我!!!

思路

  • 很明显的一道关键路径的题(光看名字就知道了吧QAQ)
  • 拓扑排序的作用->1.将所有当前父节点(入度为0)压入队列…如果是直接用bfs存放临近的节点就没有全对了QAQ,因为这个点还不一定是父节点(这里的父节点不一定是开始就是父节点,可以是删除了之前的父节点后得到的父节点,也就是删除节点的下一个节点(拗口…))上图…

在这里插入图片描述
可见1节点是当前的父节点(入度为0,没有其他边指向他了)
删除后~
在这里插入图片描述
可见现在又多出了2,3这两个父节点(入度为0)
继续这样下去就行了~~~
不过可能有些小伙伴会问:
怎么判断之后的节点入度为0啊
很简单,我们每次找着这个父节点的边后,以上图为例
1为父节点,此时入度为0
找它的后面节点(有边),发现2,3这两个节点,然后我们把2,3节点的入度减一
如果当前这个节点减一后入度为0,那么就进入队列

不过,可能又有同学会问:
啊哈?我用bfs不也是一样的么?
前面这个样例当然是一样的…
但如果…
在这里插入图片描述
这时候,我们第一次只能判断1这个节点两个边,对应2,3咯
如果bfs的话…
此时3节点入度不为0…但是bfs却笨笨地将其放入队列.

此时
又有人问:
为什么要入度为0???[抠鼻]
你个小笨蛋…不好好看题目,都说了要完成1,5两个任务才能完成3这个任务啦
所以入度为零刚好对应这个情况…

大致思路就是这样惹…

AC代码

注释的地方直接忽略吧…

#include <iostream>
#define inf 100000
using namespace std;
int a[105][105];                                //图
int dic[105];		//并查集无法判断有向图的连通 
int in[105],out[105];
/*
int Find(int x)
{
    if(x == dic[x])
        return x;
    return dic[x] = Find(dic[x]);
}
bool merge(int x,int y)
{
    x = Find(x);
    y = Find(y);
    if(x != y)                  //还未连通
    {
        dic[y] = x;
        return true;
    }
    //已经连通,再连就是环
    return false;
}
*/
/*
void ini(int n)
{
	for(int i=1;i<=n;i++)
	dic[i] = i;
}
*/
int book[105];
void ini(int n)
{
	for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
        a[i][j] = inf;
}
bool dfs(int cur,int n)
{
	book[cur] = 1;
	
	for(int i = 1;i<=n;i++)
	if(a[cur][i] != inf)
	{
		if(book[i] == 1 || dfs(i,n))
		return true;
	}
	book[cur] = 0;
	return false;
}
/*
int first(int n)                    //找起点
{
	int i;
	for(i=1;i<=n;i++)
	if(in[i] == 0)
	return i; 
} 
int last(int n)                     //找终点
{
	int i;
	for(i=1;i<=n;i++)
	if(out[i] == 0)
	return i;
}
//后来发现其实可以用出入度来表示起点终点
出度:
就是当前这个点和其他点连线的个数(有向)
入度:
其他点到当前点的个数(有向)
*/
int main()
{
    int n,m,i,j;
    scanf("%d %d",&n,&m);
    
    ini(n);
    for(i=0;i<m;i++)
    {
        int x,y,v;
        scanf("%d %d %d",&x,&y,&v);
        a[x][y] = v;
        in[y]++;
		out[x]++; 
    }
    
    //int fir = first(n);
    //int las = last(n);
    //dfs判断是否有环->这里其实也可以用后面第一个拓扑排序来判断...
    //具体判断方法就是,记录访问点的个数,如果有环的话,所有的点都不能全部访问,
    //或者说,这个环的一个起点永远是个谜(入度不为零)...可以按照1->2 2->3 3->1的方式来模拟
	if(dfs(1,n))
	{
		printf("0");
		return 0;
	}//dfs判别法
    ///*
    int fast[n+1] = {0};                              //最早开始做工的时间
    int late[n+1] = {0};                              //可以最晚开工的时间
    
    int que[1005],head = 0,tail = 0;
    
    //注意起点不一定是1 也可能多起点
    //fast[fir] = 0;
    int count = 0;                      //访问点的个数
   	for(i=1;i<=n;i++)
        if(in[i] == 0)
        que[tail++] = i;
    
    while(head != tail)
    {
        int x = que[head];
        head++;
        count++;
        for(i=1;i<=n;i++)
        {
            if(a[x][i] != inf)
            {
                in[i]--;
                if(fast[i] < a[x][i] + fast[x])
                    fast[i] = a[x][i] + fast[x];
                if(in[i]==0)    que[tail++] = i;
            }
        }
    }
    //拓扑排序判别法->与上头的dfs任选其一
    if(count != n)
    {
        printf("0");
        return 0;
    }
    int more = 0;
    for(i=1;i<=n;i++)
    if(fast[i] > more)
        more = fast[i];
    printf("%d\n",more);
    
    //开始求最晚开工时间
	head = 0,tail = 0;
	//que[tail++] = las;
	//初始化late
	for(i=1;i<=n;i++)
    {
        late[i] = inf;
        if(out[i] == 0)
            que[tail++] = i,late[i] = more;
    }
	 
	//late[las] = fast[las];
	while(head != tail)
	{
		int x = que[head];
		head++;
		for(i=n;i>=1;i--)
		{
			if(a[i][x] != inf)
			{
                out[i]--;
				if(late[i] > late[x] - a[i][x])
				late[i] = late[x] - a[i][x];
				if(out[i] == 0)    que[tail++] = i;
			}
		}
	}
    //*/
    //bfs求late 和 fast答案会错误
    //改用拓扑排序
	
	for(i=1;i<=n;i++)
	{
		for(j=n;j>=1;j--)
		{
			if(a[i][j] != inf && late[j]-fast[i] == a[i][j])
			printf("%d->%d\n",i,j);
		}
	}
	
	//for(i=0;i<top;i++)
	//printf("%d ",path[i]);
	
    return 0;
}

--------------------------------------------------------------------------end

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值