CF1726D Edge Split 题解

21 篇文章 0 订阅

2024/6/28UPD CSDN又擅自给我改VIP文章:(

分析

由于要求的是连通块数量最小,那么我们首先可以想到生成树,如果将生成树染成一个颜色,那么这个颜色的连通块就只有一个了,那就做完了

为什么染成生成树一定最优呢?

我们令红边的连通块数记为 c 1 c_1 c1,记蓝边连通块数记为 c 2 c_2 c2

首先,我们观察到一点,假设红边组成的子图中存在一个,我们将环上的其中一条边染成蓝色, c 1 c_1 c1 不会发生变化,而 c 2 c_2 c2 最多会减少 1 1 1,因此最终答案中一定不能存在任何一个环。
在这里插入图片描述

而如果不存在这样的一个环,则如果将一条红边染成蓝色, c 1 c_1 c1 最多增加 1 1 1 c 2 c_2 c2 最少减少 1 1 1

在这里插入图片描述

因此我们让任何一个颜色组成一棵生成树一定是不亏的。

我们假定组成生成树的是红边。由于树的性质,我们知道他一共包含了 n − 1 n - 1 n1 条边,而题目保证 m ⩽ n + 2 m \leqslant n + 2 mn+2,这就说明了蓝边最多只有 3 3 3 条边,也就是蓝边最多只可能形成一个环 ( 因为要形成两个环至少需要 5 5 5 条边 )。因此,我们就只需要考虑怎么把这个环断开就行了 ( 至于为什么,看上面 )。

实际上这个问题非常简单,因为我们的红色边已经构成了一棵生成树了,因此我们只需要找到这换上三个点当中最深的那个,将他原本连接的任意一条蓝色边染成红色,再将他与他原本生成树上的父亲的红色边染成蓝色即可。这样做既可以保证红色边仍然形成一棵生成树,也可以把环断开,具体方法如图。
在这里插入图片描述
至于为什么随便断环会错我也不知道,希望有大佬能证出来Qwq。

那么这样就很简单了,我们只需要 DFS 求出生成树,再断环即可。

注意

千万别用memset!!!
千万别用memset!!!
千万别用memset!!!

代码

小心哦~
代码里是有坑的哦~
放心~
不是算法上的坑~

//省略快读和头文件
int n, m;
struct Edge {
	int hd[MAXN];
	int nxt[MAXN << 1], to[MAXN << 1];
	int pre[MAXN << 1];
	int col[MAXN << 1];//1R0B
	int tot = 1;
	
	void Add(int x, int y) {
		nxt[++tot] = hd[x];
		hd[x] = tot;
		to[tot] = y;
		pre[tot] = x;
	}
}e;

bool vis[MAXN];
vector<int> B;
int deg[MAXN];
int pa[MAXN], dpt[MAXN];
void dfs(int x, int fa) {//构建生成树
	vis[x] = true;
	pa[x] = fa;
	dpt[x] = dpt[fa] + 1;
	for(int i = e.hd[x]; i; i = e.nxt[i]) {
		int y = e.to[i];
		if(vis[y])
			continue;
		e.col[i] = e.col[i ^ 1] = 1;
		dfs(y, x);
	}
}

void Check() {
	for(int i = 2; i <= m * 2; i += 2)
		if(!e.col[i]) {
			B.push_back(e.pre[i]);
			B.push_back(e.to[i]);
			deg[e.pre[i]]++, deg[e.to[i]]++;
		}
	
	if(B.size() < 6)
		return ;
	for(auto it : B)//用于检查是否成环
		if(deg[it] < 2)
			return ;
	
	priority_queue<pair<int, int>> q;//用于找深度最大
	for(auto it : B)
		q.push(make_pair(dpt[it], it));
		
	int x = q.top().second;//取出这三个点中深度最深的 
	int l, r;//交换 l 和 r 的颜色 
	for(int i = e.hd[x]; i; i = e.nxt[i]) {
		int y = e.to[i];
		
		if(e.col[i] == 0)
			l = i;
		if(y == pa[x])
			r = i;
	}
	e.col[l] = e.col[l ^ 1] = 1;
	e.col[r] = e.col[r ^ 1] = 0;
}

int T;

int main()
{
	T = inpt();
	while(T--) {
//		memset(e.hd, 0, sizeof(e.hd));
//		memset(e.col, 0, sizeof(e.col));
//		memset(pa, 0, sizeof(pa));
//		memset(dpt, 0, sizeof(dpt));
//		e.tot = 1;
		
		n = inpt(), m = inpt();
		for(int i = 1; i <= m; ++i) {
			int x = inpt(), y = inpt();
			
			e.Add(x, y);
			e.Add(y, x);
		}
		
		memset(deg, 0, sizeof(deg));
		B.clear();
		memset(vis, false, sizeof(vis));
		dfs(1, 0);
		
		Check();
		
		for(int i = 2; i <= 2 * m; i += 2)
			printf("%d", e.col[i]);
		puts("");
		
		for(int i = 1; i <= n; ++i) {
			e.hd[i] = 0;
			pa[i] = 0;
			dpt[i] = 0;
			vis[i] = false;
			deg[i] = 0;
		}
		for(int i = 2; i <= e.tot; ++i) {
			e.col[i] = 0;
		}
		e.tot = 1;
	}
	

 	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值