题目
题目描述
给出
n
n
n 个点、
m
m
m 条边的无向图,问有多少条边满足:如果将这条边删去,得到的图存在点的二染色,并且这条边的两个端点同色。
数据范围与约定
2
≤
n
≤
1
0
5
2\le n\le 10^5
2≤n≤105 且
1
≤
m
≤
2
×
1
0
5
1\le m\le 2\times 10^5
1≤m≤2×105 。不保证图是连通的,不保证没有重边,保证无自环。虽然有自环也是可做的。
思路
首先,为了使得图存在二染色,删掉的边必须是所有奇环的公共边。
其次,何时图的二染色会不能使得端点同色?显然是新图上,两点之间存在一条长度为奇数的路径。那么加上这条边,也就是原图的一个偶环。所以删掉的边不能在偶环上。
看上去我们要找出所有的环么?有一个很神奇的事情是,只需要找出若干本原环 即可。什么是本原环?下面会讲。其核心思想是,当环 A , B A,B A,B 的条件都满足时,环 C = A ⊕ B C=A\oplus B C=A⊕B 的条件就得到了满足,其中 ⊕ \oplus ⊕ 表示对称差。
首先试试怎么裂解一个环。先建出一个 d f s \rm dfs dfs 树。若一个环经过了至少 2 2 2 条非树边,那就会把环划分成至少 2 2 2 个段(每个段都只走树边)。从这两个段里任取两个点,二者之间有一条完全由树边组成的路径;这条路径可以与环完全无交(如果有交集,必定在两端,改变起点、终点即可)。
于是很显然的,沿着这条路径,将左边和右边分别造出一个环。显然这两个环的对称差就是整个环,而且这两个环包含的非树边的数量严格更小。于是我们发现:本原环就是只含一条非树边的环。
那么环 A , B A,B A,B 的条件满足时,环 C = A ⊕ B C=A\oplus B C=A⊕B 的条件是否满足呢?这就需要讨论一下了。
- 如果 A , B A,B A,B 都是偶环:则删掉的边不能是 A ∪ B A\cup B A∪B 上的边,也就不能是偶环 C C C 上的边。
- 如果 A , B A,B A,B 都是奇环:则删掉的边必须是 A ∩ B A\cap B A∩B 上的边,而 C C C 的含义是 “只属于其中之一” 的部分,显然不包含 A ∩ B A\cap B A∩B,所以偶环 C C C 上的边也就不能选了。
- 如果 A , B A,B A,B 其中之一是奇环:不妨设 A A A 是奇环,那么只能选择 A A A 中不属于 B B B 的部分,显然这是 “只属于其中之一” 的部分,也就是 C C C 的子集。所以选择的结果肯定是奇环 C C C 上的边。
很神奇,对吧?并不是所有条件都能轻易地划分成本原环的。
那么如何判断条件呢?首先你会发现:任何一条非树边,恰好在一个本原环上;那么如果有至少两个奇环,绝对不能选择非树边。而非树边在偶环上,就更不能选了。所以唯一可以选择的非树边是:整张图只有一个奇环(本原环)。
知道了这个,我们就只需要维护树边的选择情况。由于是无向图,非树边一定是 d f s \rm dfs dfs 过程中的返祖边,所以用树上差分就可以很轻易地维护了。时间复杂度 O ( n + m ) \mathcal O(n+m) O(n+m) 。
代码
注意不要将 d f s \rm dfs dfs 树的树根的 “父边” 统计入答案,因为它压根就不存在 😅
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long int_;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MAXN = 100005;
const int MAXM = 200005;
struct Edge{
int to, nxt;
Edge() = default;
Edge(int _to,int _nxt):to(_to),nxt(_nxt){ }
};
Edge e[MAXM<<1];
int head[MAXN], cntEdge;
void addEdge(int a,int b){
e[cntEdge] = Edge(b,head[a]);
head[a] = cntEdge ++;
}
int dep[MAXN], tag[MAXN], want;
void dfs(int x){
for(int i=head[x]; ~i; i=e[i].nxt)
if(!dep[e[i].to]) dep[e[i].to] = dep[x]+1, dfs(e[i].to);
else if(dep[e[i].to] > dep[x]){
if((dep[e[i].to]^dep[x])&1) -- tag[e[i].to], ++ tag[x];
else ++ tag[e[i].to], -- tag[x], ++ want;
}
}
bool vis[MAXN];
void pushTag(int x){
vis[x] = true;
for(int i=head[x]; ~i; i=e[i].nxt)
if(vis[e[i].to] == false)
pushTag(e[i].to), tag[x] += tag[e[i].to];
}
int main(){
int n = readint(), m = readint(), ans = 0;
memset(head+1,-1,n<<2);
for(int a,b,i=1; i<=m; ++i){
a = readint(), b = readint();
addEdge(a,b), addEdge(b,a);
}
rep(i,1,n) if(!dep[i]) dep[i] = 1, dfs(i);
rep(i,1,n) if(!vis[i]) pushTag(i), ans -= (tag[i] == want);
rep(i,1,n) if(tag[i] == want) ++ ans;
if(want == 1) ++ ans; // the edge not on the tree
printf("%i\n",ans);
return 0;
}