题意:一个\(n\)个点的竞赛图,给出\(m\)条红色的边,其方向确定,其余边均为绿色,方向未知。你可以询问不超过\(2n\)次,每次询问一条绿色边的方向。要求找到一个点\(x\),使得\(x\)出发到任意一个点都有至少一条同色路径。\(n ,m\leq 10^5\)。可能会在交互过程中动态构造图。
考虑没有红色的边时怎么做。显然在询问过程中会形成若干棵绿色的外向树,每次询问两棵外向树的树根,将它们合并起来即可。最后剩余的点即为答案。
回到原题,发现由于红色边的存在导致有些边无法通过询问定向,但是红色边本身可以作为连通的方式。
将红色连通块缩点,发现此时任何没有红色入度的点均与上文中的绿色外向树等价,即,这些点满足可以任意询问两两之间的边,且这样的点只剩一个时即为答案。
另一个值得注意的点是,合并时被删除的点可能会有若干红色出边,此时需遍历这些边,并将新的满足条件的点加入待处理点集中。
Code:
#include <queue> #include <cstdio> #include <cctype> #include <cassert> #include <cstring> #include <iostream> #include <algorithm> #define R register #define ll long long using namespace std; const int N = 110000; int hd[N], nxt[N], to[N], dfn[N], low[N], scc[N], dgrV[N], dgrS[N], stck[N], instck[N], rep[N]; int n, m, cnt, tot, noedg = 1, top; queue<int> que; template <class T> inline void read(T &x) { x = 0; char ch = getchar(), w = 0; while (!isdigit(ch)) w = (ch == '-'), ch = getchar(); while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); x = w ? -x : x; return; } inline void addEdg(int x, int y) { nxt[++noedg] = hd[x], hd[x] = noedg, to[noedg] = y; return; } void tarjan(int now) { dfn[now] = low[now] = ++cnt; stck[++top] = now, instck[now] = 1; for (R int i = hd[now]; i; i = nxt[i]) { int v = to[i]; if (dfn[v]) { if (instck[v]) low[now] = min(low[now], dfn[v]); } else tarjan(v), low[now] = min(low[now], low[v]); } if (dfn[now] == low[now]) { rep[++tot] = now; while (stck[top] != now) instck[stck[top]] = 0, scc[stck[top--]] = tot; instck[now] = 0, scc[stck[top--]] = tot; } return; } inline void del(int x) { for (R int i = hd[x], v; i; i = nxt[i]) { if (scc[v = to[i]] != scc[x]) { if (!--dgrS[scc[v]]) que.push(rep[scc[v]]); } else if (!--dgrV[v] && v != rep[scc[v]]) que.push(v); } return; } int main() { int x, y; read(n), read(m); for (R int i = 1; i <= m; ++i) read(x), read(y), addEdg(x, y); for (R int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i); for (R int i = 1; i <= n; ++i) for (R int j = hd[i], v; j; j = nxt[j]) if (scc[v = to[j]] != scc[i]) ++dgrS[scc[v]]; else ++dgrV[v]; for (R int i = 1; i <= tot; ++i) if (!dgrS[i]) que.push(rep[i]); while (que.size()) { if (que.size() == 1) { cout << "! " << que.front() << endl; return 0; } int a = que.front(), b; que.pop(), b = que.front(), que.pop(); cout << "? " << a << ' ' << b << endl; read(x); if (x) del(b), que.push(a); else del(a), que.push(b); } return 0; }