考虑翻转 u \rightarrow v 这条边对于强连通分量数量的影响,只需要考虑下面两个因素就行了:
原图中存在一条 v \rightarrow u 的路径。
原图中存在一条 u \rightarrow v 的路径,且不含上面的那条边。
若上述两个因素中有且仅有一个满足,则强连通分量数量会变化。
考虑原因:
若两个条件均不满足:则说明这个点不管正着还是反着,都不会有强连通分量产生,则不会变化。
若两个条件均满足:则这两条路径拼起来就是一个强连通分量,缩点之后和 u \rightarrow v 这条边的方向没有关系了,所以也不会变化。
若仅有一个条件满足,则说明,如果没有 u \rightarrow v 这条边,则原图中 u 和 v 一定不在同一个强连通分量中。而如果有了 u \rightarrow v 或者 v \rightarrow u 这条边,则分别结合条件 1 和条件 2,就能让 u 和 v 在同一个强连通分量中,则说明翻转边一定会让强连通分量的数量改变。
然后考虑如何统计。
首先第一个条件直接 O(NM) 爆搜统计就行了。
第二个条件乍一看有点辣手,但是仔细想想就会发现,它本质上就只是求:u \rightarrow v 的路径中,这条边是不是必经边。你就考虑从 x 开始 DFS,记录 x 经过的点,被遍历到的时候 x 的出边的标号。然后考虑将 x 的出边顺序反过来再 DFS 一遍。如果两次遍历到的时候记录下来的标号不同,则说明 u \rightarrow v 并不是必经边。反之则一定是必经边。
然后就枚举每条边瞎判判就行了。
```cpp
const int MAXN = 1010;
const int MAXM = 200010;
struct Edge {
int u, v;
Edge() {}
Edge(int u, int v):u(u), v(v) {}
}E[MAXM];
vector<int>to[MAXN];
int Mark[MAXN];
bitset<MAXN>vis;
bool G1[MAXN][MAXN];
bool G2[MAXN][MAXN];
inline void dfs1(int x, int st) {
G1[st][x] = 1, vis.set(x);
for(auto u : to[x]) if(!vis[u]) dfs1(u, st);
}
inline void dfs2(int x, int col, int k, int st) {
if(k) G2[st][x] = Mark[x] != col;
else Mark[x] = col; vis.set(x);
for(auto u : to[x]) if(!vis[u]) dfs2(u, col, k, st);
}
int main() {
int n = ri, m = ri;
for(int i = 1; i <= m; i++) {
int u = ri, v = ri;
to[u].push_back(v);
E[i] = Edge(u, v);
}
for(int i = 1; i <= n; i++) vis.reset(), dfs1(i, i);
for(int i = 1; i <= n; i++) {
vis.reset(), vis.set(i);
memset(Mark, 0, sizeof(Mark));
int d = to[i].size();
for(int j = 0; j < d; j++) if(!vis[to[i][j]]) dfs2(to[i][j], j + 1, 0, i);
vis.reset(), vis.set(i);
for(int j = d - 1; ~j; --j) if(!vis[to[i][j]]) dfs2(to[i][j], j + 1, 1, i);
}
for(int i = 1; i <= m; i++)
puts(G1[E[i].v][E[i].u] ^ G2[E[i].u][E[i].v] ? "diff" : "same");
return 0;
}
```