「解题报告」CF1142E Pink Floyd
比较神奇的题目。
首先考虑没有粉色边怎么做。我们可以按照连通块的思路来考虑,每次合并两个连通块。发现,只要我们合并两个连通块的根,最后一定会得到一棵外向树,而这样合并最后一定能够合并成一棵外向树,于是就可以在 \(n - 1\) 次询问内得到答案。
考虑有粉色边怎么做。粉色边的问题其实就是导致有一些边不能够被选。那么其实我们只需要限制上面的算法不会选取存在粉色边的两个点即可。
直接限制好像并不好限制,那么我们可以考虑维护出一个集合,使得这个集合之间的每两个点之间都没有粉色边,这样任选两个点都不会选到粉色边了。
考虑缩点然后拓扑排序。这样对于入度为 \(0\) 的点之间肯定没有粉色边,这样每次直接选两个点连就行了。对于一个强连通分量中的点,我们可以随意定一个顺序,只需要保证同一个连通块内只有一个点在当前集合中即可。考虑最后仅剩下一个点的时候,由于是拓扑排序,那么这个点一定能通过粉色边到达所有没被删除过的点,那么直接选取剩下的这一个点就可以了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n, m;
bool query(int u, int v) {
printf("? %d %d\n", u, v);
fflush(stdout);
int ret; scanf("%d", &ret);
return ret;
}
vector<int> e1[MAXN], e2[MAXN];
bool vis[MAXN], instack[MAXN];
int deg[MAXN];
void dfs(int u) {
vis[u] = instack[u] = 1;
for (int v : e1[u]) {
if (!instack[v]) e2[u].push_back(v), deg[v]++;
if (!vis[v]) dfs(v);
}
instack[u] = 0;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
e1[u].push_back(v);
}
for (int i = 1; i <= n; i++) if (!vis[i])
dfs(i);
vector<int> p;
for (int i = 1; i <= n; i++) if (!deg[i])
p.push_back(i);
while (p.size() > 1) {
int u = p.back(); p.pop_back();
int v = p.back(); p.pop_back();
if (!query(u, v)) swap(u, v);
p.push_back(u);
for (int w : e2[v]) {
deg[w]--;
if (!deg[w]) p.push_back(w);
}
}
printf("! %d\n", p.back());
return 0;
}