[usOJ4888]電圧

122 篇文章 0 订阅
98 篇文章 0 订阅

题目

传送门 to usOJ

题目描述
给出 n n n 个点、 m m m 条边的无向图,问有多少条边满足:如果将这条边删去,得到的图存在点的二染色,并且这条边的两个端点同色。

数据范围与约定
2 ≤ n ≤ 1 0 5 2\le n\le 10^5 2n105 1 ≤ m ≤ 2 × 1 0 5 1\le m\le 2\times 10^5 1m2×105 。不保证图是连通的,不保证没有重边,保证无自环。虽然有自环也是可做的

思路

首先,为了使得图存在二染色,删掉的边必须是所有奇环的公共边。

其次,何时图的二染色会不能使得端点同色?显然是新图上,两点之间存在一条长度为奇数的路径。那么加上这条边,也就是原图的一个偶环。所以删掉的边不能在偶环上。

看上去我们要找出所有的环么?有一个很神奇的事情是,只需要找出若干本原环 即可。什么是本原环?下面会讲。其核心思想是,当环 A , B A,B A,B 的条件都满足时,环 C = A ⊕ B C=A\oplus B C=AB 的条件就得到了满足,其中 ⊕ \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=AB 的条件是否满足呢?这就需要讨论一下了。

  • 如果 A , B A,B A,B 都是偶环:则删掉的边不能是 A ∪ B A\cup B AB 上的边,也就不能是偶环 C C C 上的边。
  • 如果 A , B A,B A,B 都是奇环:则删掉的边必须是 A ∩ B A\cap B AB 上的边,而 C C C 的含义是 “只属于其中之一” 的部分,显然不包含 A ∩ B A\cap B AB,所以偶环 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值