Problem
Northwestern European Regional Contest 2016
vjudge.net/problem/Gym-101170I
Meaning
一幅 n 个点的有向图,m 个点有铁矿、k 个点有煤,你一开始在 1 号点,至少要占领一个铁和一个煤,问至少要占领多少个点(除了开始的 1 号点外)
Analysis
应该要尝试找到任意一个铁,再从这个铁出发找最近的煤(或者反过来),但这样太暴离力了。其实就是 点到起点最短距离、点到最近铁、点到最近煤 三个距离的和的最小值。
理解题意时有过分歧:是第一人称视角还是第三人称视角(我们当时叫个人视角和上帝视角)。如果是个人视角,那就只能从当前所在点为起点往外走;如果是上帝视角,则可以从任意一个已占领的点为起点往外走。如样例:
3 1 1
2
3
2 2 3
0
0
个人视角是impossible
,上帝视角则是2
。
从 dalao 的代码来看,是上帝视角。
dalao 的做法是建两幅图,一幅存正向边,用来算从起点到其它点的最短距离(单源点bfs);另一幅存反向边,用于求点到最近铁/煤的距离(全部铁/煤点同时当起点开始bfs),然后遍历所有点,把 3 个距离加和求最小值。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int N = 200000, M = N, K = N, BIG = 123456789;
int s[] = {1}, o[M], c[K]; // source point
int dis[3][N+1];
vector<int> edge[N+1], rev[N+1];
queue<int> que;
void bfs(int *src, int n, int num, int id, vector<int> *e)
{
for(int i = 0; i <= num; ++i)
dis[id][i] = BIG;
for(int i = 0; i < n; ++i)
{
dis[id][src[i]] = 0;
que.push(src[i]);
}
for(int t; !que.empty(); que.pop())
{
t = que.front();
for(int i = 0; i < e[t].size(); ++i)
if(dis[id][e[t][i]] == BIG)
{
dis[id][e[t][i]] = dis[id][t] + 1;
que.push(e[t][i]);
}
}
}
int main()
{
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for(int i = 0; i < m; ++i)
scanf("%d", o+i);
for(int j = 0; j < k; ++j)
scanf("%d", c+j);
for(int i = 1, a; i <= n; ++i)
{
scanf("%d", &a);
for(int j = 0, b; j < a; ++j)
{
scanf("%d", &b);
edge[i].push_back(b);
rev[b].push_back(i);
}
}
bfs(s, 1, n, 0, edge);
bfs(o, m, n, 1, rev);
bfs(c, k, n, 2, rev);
int ans = BIG;
for(int i = 1; i <= n; ++i)
ans = min(ans, dis[0][i] + dis[1][i] + dis[2][i]);
if(ans == BIG)
puts("impossible");
else
printf("%d\n", ans);
return 0;
}