又是一道交互题
题目描述:你有一个 \(n\) 个点的竞赛图,有 \(m\) 条边为粉红色,其余为绿色。粉红色边的方向已知,绿色边方向未知,但你可以询问不超过 \(2n\) 次一条边 \(\{u,v\}\),交互器会告诉你这条边的方向。求一个点 \(u\),使得对于任意 \(v\neq u\),\(u\) 可以只经过一种颜色的边到达 \(v\)。
数据范围:\(n,m\le 10^5\)。
解法实在有点绕...
首先如果 \(m=0\),我们维护一个答案集合 \(S\) 表示目前可能的答案。一开始 \(S=V\),之后要一步步排除。若 \((u,v)\in E\),则 \(v\) 是答案 \(\Rightarrow u\) 是答案。所以可以不考虑 \(v\),把 \(v\) 从 \(S\) 中删掉。最后只剩下一个点就是答案。
但是如果有粉红色边的话,有一些边就不能用了,但是答案点可以通过粉红色边与其他点联通。所以先把粉红色边缩点,每个强连通分量中只取出一个点加入初始的 \(S\),然后按照上面的方法做。
但是如果 \((u,v)\in E\) 则不能直接将 \(v\) 删掉,因为 \(v\) 所在的强连通分量由粉红色边组成,不能和绿色边在一条路径中。
于是你可以把每个强连通分量中的一些边删掉使得它成为一个 DAG,每次把入度为 \(0\) 的点加入,询问得到 \((u,v)\in E\) 时把 \(v\) 删掉,再看剩下的点中有没有入度为 \(0\) 的点加入。剩下的最后一个点就是答案。
正确性证明:被删过的点可以通过绿边到达,没有被删过的点可以通过粉红色边到达。于是 \(n-1\) 次询问就可以直接搞定?
#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define PB push_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int N = 100003, mod = 998244353;
inline int ksm(int a, int b){
int res = 1;
for(;b;b >>= 1, a = (LL) a * a % mod) if(b & 1) res = (LL) res * a % mod;
return res;
}
template<typename T>
inline void read(T &x){
int ch = getchar(); x = 0; bool f = false;
for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
if(f) x = -x;
}
template<typename T>
inline bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
inline bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m, cnt, head[N], to[N], nxt[N], deg[N];
bool ins[N], vis[N];
vector<int> E[N], now;
inline void add(int a, int b){to[++ cnt] = b; nxt[cnt] = head[a]; head[a] = cnt;}
void dfs(int x){
ins[x] = vis[x] = true;
for(Rint i = head[x];i;i = nxt[i]){
if(!ins[to[i]]){E[x].PB(to[i]); ++ deg[to[i]];}
if(!vis[to[i]]) dfs(to[i]);
}
ins[x] = false;
}
int main(){
read(n); read(m);
for(Rint i = 1, a, b;i <= m;++ i){
read(a); read(b); add(a, b);
}
for(Rint i = 1;i <= n;++ i) if(!vis[i]) dfs(i);
for(Rint i = 1;i <= n;++ i) if(!deg[i]) now.PB(i);
while(now.size() > 1){
int u = now.back(); now.pop_back();
int v = now.back(), f; now.pop_back();
printf("? %d %d\n", u, v); fflush(stdout);
scanf("%d", &f); if(f) swap(u, v); now.push_back(v);
for(Rint w : E[u]) if(!-- deg[w]) now.PB(w); E[u].clear();
}
printf("! %d\n", now.front()); fflush(stdout);
}