Codeforces Round 847 (Div. 3) G.Tokens on Graph (构造)
题目大意
给定一个无向图,我们定义图中有四种点,一种是黑色的点表示终点,并且黑色的点始终是1号点,一种是红色的点,一种是灰色的点,最后一种就是没有颜色的点。
游戏规则:我们必须移动任意一个灰色的点到相邻的点上,如果灰色的点移动到了红色的点上,那么我们可以移动其他灰色的点继续上述操作(不能移动自己),否则游戏结束。如果我们最终能使灰色的点到达终点,那么输出YES,如果不能使灰色的的点到达终点,输出NO。一下图为例:
我们这么移动8->6,7->5,6->4, 5->6, 4->2, 6->4, 2->1游戏结束到达终点输出YES。
输入一个整数T,表示有T组测试数据(1≤T≤1e4)。测试用例由一个空字符串分隔
输入n和m表示有n个点m个边 (1≤n≤2e5 , 0≤m≤2e5),并且保证图一定联通,任何点之间都是可以到达的。
输入p和b表示有p个黑色的点和b个红色的点。其中(1≤p≤n,0≤b≤n )。也就是说可能没有红色的点。
输入p个两两不同的灰色的点,表示灰色的点所在的位置。
输入b个红色的点,表示红色点所在的位置(灰色点可以和红色点重合,并且一个点上可以有多个灰色点,如果b为0则这一行为空)。
输入m行u,v表示u和v之间有一条边。
可以保证所有测试用例的n之和不超过2e5。同样地,保证所有输入数据集的m之和不超过2e5
解题思路:
如果有灰色的点在终点直接输出YES,或者终点附近有灰色的点直接输出YES。
我们发现一个性质,那就是如果一个灰色的点能走到红色的点,并且这个红色的点周围也有红色的点,那么我们就可以一直在这两个红色的点之间一直走下去。所以我们把红色的点分为两类:
1.可以走无限步数的红色的点。
2.只能走一步的红色的点。
我们从终点出发找到所有能到达的灰色的点,并将这些点的编号和离终点的距离丢进一个vector<pair<int, int>> use;数组里面。
如果use为空那么一定无解。
我们把use内的第一个元素的编号定义为st,第一个元素到终点的距离定义为sum。我们从其他所有灰色点出发,如果这些灰色的点能走出第一种红色的点那么输出YES,如果这些灰色的点能走的第二种点的数量大于等于sum-1,那么也输出YES。但是我们第一个元素不一定就是能走到终点的点,因为我们可能第一个元素能走出第一种点,那么如果存在第二个元素也可以直接输出YES。
#include <bits/stdc++.h>
const int N = 2e5 + 10, M = N << 2;
int n, m;
typedef long long ll;
const int INF = 0x3f3f3f3f;
typedef std::pair<int, int> PII;
int h[N], ne[N << 1], e[N << 1], idx;
inline void add(int a, int b) {
ne[idx] = h[a], e[idx] = b, h[a] = idx ++;
}
inline void slove() {
std::cin >> n >> m;
std::vector<bool> grey(n, 0), red(n, 0);//i号点是不是灰色点或者红色点
idx = 0;
for (int i = 0; i < n; i ++) h[i] = -1;
int p, b;
std::cin >> p >> b;
std::vector<int> point;//存储所有灰色点编号
for (int i = 1; i <= p; i ++) {
int x;
std::cin >> x;
x --;
grey[x] = true;//给灰色点打标记
point.push_back(x);
}
for (int i = 1; i <= b; i ++) {
int x;
std::cin >> x;
x --;
red[x] = true;//给红色点打标记
}
while (m --) {
int a, b;
std::cin >> a >> b;
a --, b --;
add(a, b);
add(b, a);
}
std::vector<bool> good(n, 0);//本身是红色周围也有红色,那么到达这个点可以一直走下去。
for (int i = 0; i < n; i ++) {//找到本身是红色邻边也为红色的点
if (red[i]) {
bool f = false;
for (int j = h[i]; ~j; j = ne[j]) {
int k = e[j];
if (red[k]) {
f = true;
break;
}
}
if (f) good[i] = true;
}
}
if (grey[0]) {//如果灰色点在终点直接输出YES
std::cout << "YES" << '\n';
return ;
}
std::vector<PII> use;//记录能到达终点的灰色点的编号和到终点的距离
int st = -1, sum = 0;//记录离终点最近的点的编号和距离
auto bfs = [&]() -> void {
std::queue<PII> q;
std::vector<bool> vis(n);
q.push({0, 0});
vis[0] = true;
while (!q.empty()) {
PII u = q.front();
q.pop();
for (int i = h[u.first]; ~i; i = ne[i]) {
int j = e[i];
if (vis[j]) continue;
if (grey[j]) {
if (st == -1) {
st = j;
sum = u.second + 1;
}
use.push_back({st, u.second + 1});
}
if (red[j]) {
q.push({j, u.second + 1});
vis[j] = true;
}
}
}
};
bfs();
if (st == -1) {//如果没有灰色点可以到达终点直接输出NO即可
std::cout << "NO" << '\n';
return ;
}
bool ok = false;//ok表示是否有一个灰色的可以无限走(即能走到good的点上)
int step = 0;
for (auto &u: point) {
if (u != st) {//判断其他点是能走的步数。
bool f = false;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (good[j]) {
ok = true;
break;
}
if (red[j]) f = true;
}
if (f) step ++;
} else {//如果离终点最近的点可以无限走,并且还有其他可以到达终点的点直接输出YES
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (ok && use.size() > 1) {
std::cout << "YES" << '\n';
return ;
}
}
}
if (ok) break;
}
if (ok || sum == 1) std::cout << "YES" << '\n';
else if (step >= sum - 1) std::cout << "YES" << '\n';
else std::cout << "NO" << '\n';
}
int main(void) {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while (_ --) slove();
return 0;
}