P2752 [USACO4.3] 街道赛跑Street Race 题解

题目传送门

题面

题目描述

图一表示一次街道赛跑的跑道。可以看出有一些路口(用 0 0 0 N N N 的整数标号),和连接这些路口的箭头。路口 0 0 0 是跑道的起点,路口 N N N 是跑道的终点。箭头表示单行道。运动员们可以顺着街道从一个路口移动到另一个路口(只能按照箭头所指的方向)。当运动员处于路口位置时,他可以选择任意一条由这个路口引出的街道。

图一:有 10 10 10 个路口的街道

一个良好的跑道具有如下几个特点:

  1. 每一个路口都可以由起点到达。
  2. 从任意一个路口都可以到达终点。
  3. 终点不通往任何路口。

运动员不必经过所有的路口来完成比赛。有些路口却是选择任意一条路线都必须到达的(称为“不可避免”的)。在上面的例子中,这些路口是 0 0 0 3 3 3 6 6 6 9 9 9。对于给出的良好的跑道,你的程序要确定“不可避免”的路口的集合,不包括起点和终点。

假设比赛要分两天进行。为了达到这个目的,原来的跑道必须分为两个跑道,每天使用一个跑道。第一天,起点为路口 0 0 0,终点为一个“中间路口”;第二天,起点是那个中间路口,而终点为路口 N N N。对于给出的良好的跑道,你的程序要确定“中间路口”的集合。如果良好的跑道 C C C 可以被路口 S S S 分成两部分,这两部分都是良好的,并且 S S S 不同于起点也不同于终点,同时被分割的两个部分满足下列条件:(1)它们之间没有共同的街道(2) S S S 为它们唯一的公共点,并且 S S S 作为其中一个的终点和另外一个的起点。那么我们称 S S S 为“中间路口 ”。在例子中只有路口 3 3 3 是中间路口。

输入格式

输入文件包括一个良好的跑道,最多有 50 50 50 个路口, 100 100 100 条单行道。

共有 N + 2 N+2 N+2 行,前面 N + 1 N+1 N+1 行中第 i i i 行表示以编号为 i − 1 i-1 i1 的路口作为起点的街道,每个数字表示一个终点。行末用 -2 作为结束。最后一行只有一个数字 -1

输出格式

第一行:跑道中“不可避免的路口”的数量,接着是这些路口的序号,序号按照升序排列。

第二行:跑道中“中间路口”的数量,接着是这些路口的序号,序号按照升序排列。

输入输出样例 #1

输入 #1

1 2 -2
3 -2
3 -2
5 4 -2
6 4 -2
6 -2
7 8 -2
9 -2
5 9 -2
-2
-1

输出 #1

2 3 6
1 3

说明/提示

题目翻译来自NOCOW。

USACO Training Section 4.3

题目大意

给你一个有向图,要求删去一个点(不能是起点或终点)后从起点到终点没有合法路径,这种点有哪些?要求删去这个点后图不连通,这个点有哪些?

题目思路

一句话思路:BFS。

对于问题一,我们可以枚举所有点,然后从起点 BFS。删去这个点即这个点不能再被访问,那我们就设这个点已被访问过。然后如果不能达到终点,说明这个点是必经之点。

对于问题二就要多考虑一下。我们知道满足问题二的点必定满足问题一,所以我们找到一个必经之点后,可以判断从他和他之后的点是否有边连接前面的点。具体做法是:第一次 BFS 时访问过的所有点都在前面,并且前面的点一定只有第一次 BFS 时访问的点。所以我们只需要从这个点开始做第二次 BFS ,如果发现连到了第一次 BFS 访问过的点就说明这个点不是中间点。

当然如果要用 Tarjan 来做问题二也不是不可以,只要找割点即可。这里不放这种思路的代码。

CODE

#include<iostream>
#include<cstring>
#include<vector>
#include<deque>
using namespace std;
int n,ans,ans2;
vector <int> pict[102];
bool visited[102],vis2[102];
bool must[102],middle[102];
deque <int> bfs;
int main()
{
	while(1)
	{
		int a;
		cin >> a;
		if(a == -1) break;
		n++;
		if(a == -2) continue;
		pict[n].push_back(a + 1);
		while(1)
		{
			int a;
			cin >> a;
			if(a == -2) break;
			pict[n].push_back(a + 1);
		}
	}
	for(int i = 2;i < n;i++)
    {
        bool flag = 0;
        bfs.clear();
        bfs.push_back(1);
        memset(visited,0,sizeof visited);
        visited[1] = 1;
        while(bfs.size())
        {
            int now = bfs.front();
            bfs.pop_front();
            for(auto j : pict[now])
            {
                if(j == i || visited[j]) continue;
                visited[j] = 1;
                bfs.push_back(j);
            }
        }
        must[i] = !visited[n];
        if(must[i])
        {
            ans++;
            bfs.clear();
            bfs.push_back(i);
            memset(vis2,0,sizeof vis2);
            vis2[i] = 1;
            bool flag2 = 1;
            while(bfs.size())
            {
                int now = bfs.front();
                bfs.pop_front();
                for(auto j : pict[now])
                {
                    if(vis2[j]) continue;
                    if(visited[j])
                    {
                        flag2 = 0;
                        break;
                    }
                    vis2[j] = 1;
                    bfs.push_back(j);
                }
            }
            if(flag2) ans2++,middle[i] = 1;
        }
    }
    cout << ans << ' ';
    for(int i = 1;i <= n;i++) if(must[i]) cout << i - 1 << ' ';
    cout << '\n';
    cout << ans2 << ' ';
    for(int i = 1;i <= n;i++) if(middle[i]) cout << i - 1 << ' ';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值