「题解」相框

这相框怕不是用来装我的遗像

(尝试不写缩进.ing)

原题出处:福建集训 2011

原题链接:link

「我的做题历程」:

step1:观察题面

「T 君不满足于焊接奇形怪状的作品,强烈的破坏欲驱使他拆掉这个作品,然后将之焊接成规整的形状。这会儿,T 君正要把这个怪物改造成一个环形,当作自己的相框。」——(变图为简单环)

原有的作品可能不是连通的。」——(联通块可能不止一个)

若不与任何焊点相连,则将这一端标号为 \(0\)」——(可能有孤点)

烧熔一个焊点:使得连接在焊点上的某些导线相分离或保持相连(可以理解为:把焊点上的导线划分为若干个类,相同类中的导线相连,不同类之间的导线相离)」——(允许拆点)。

注释:这里的「拆(熔)点」指什么?

下图即是一个熔点操作。


图 1 熔点操作示意


某些焊点甚至没有任何导线与之相连,由于 T 君只关心导线,因此这些焊点可以不被考虑。」——(边用完后没连接上的的点不必考虑)。

一句话题意:给你一张图(可能是不连通的),允许更换端点或拆点,求最少操作多少步,使得原图变成一个简单环(不必联通所有的点,但要用上所有的边)。(不知道简单环的同学这边请 → 图论相关概念

step2:思考解法

首先,我们需要承认:无向简单环上的所有点的入度和出度都等于 \(2\)

然后,从简单想起(有且仅有一个联通块时)——

若要满足题意,则对于图上任一点 \(u\),都有 \(d^-(u) = 2\),所以我们要熔掉所有度数大于 \(2\) 的点。偶点便熔成若干入度为 \(2\) 的点,奇点便熔成若干入度为 \(2\) 的点 + 一个孤立点。

熔完后,对于图上任一点 \(u\),一定有 \(d^-(u) \le 2\),即此时图中一定仅存在。这个时候要把他们变成简单环就简单的多了,只需首尾相连即可。

接着推广(有多个联通块时)——

先将所有联通块按上述方式拆解成若干条链,再把所有链连起来即可。值得注意的是,熔解过程中可能出现某一点 \(u\),其 \(d^-(u) \equiv 1 \pmod 2\) (即入度为奇数的点,指上文的奇点),其熔解后多出来的孤立点需要和另一入度为 \(1\) 的点熔合;而对于熔合时出现的入度为 \(2\) 的点,。

还有 原本就孤立的点(自环或无边) 需要处理。当其他连通块拆解后仅剩链,没有多余的点来连接,而此时又有多余的边时,我们需要新建一个点与其连接。(因为题目只要求边连完,多加几个点也是没有问题的)

step3:完成代码

整合前面的分析,我们可以得出:ans = 奇点数除以二 + 偶点操作总数 + 入度大于 \(2\) 的点数。

代码(抵制学术不端行为,拒绝 Ctrl + C):
#include <cstdio>
#include <algorithm> 
using namespace std;
const int N = 1e6 + 5, M = 2e5 + 5;
int n, m, a, b, d[N], tot, head[N], out[N], id[N], cnt1[N], cnt2[N], cnt;
struct Edge {
	int to, next;
} e[M << 1];
void add(int u, int v) {
	e[++tot].next = head[u], e[tot].to = v, head[u] = tot;
	return;
}
void dfs(int node) { // 也可用并查集
	id[node] = cnt; // 区分不同的连通块
	for (int i = head[node]; i; i = e[i].next) {
		int y = e[i].to;
		if (!id[y]) {
			dfs(y);	
		}
	}
	return;
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &a, &b);
		if (!a) a = ++n; // 为孤立的点建点配对
		if (!b) b = ++n;
		d[a]++;
		d[b]++;
		add(a, b);
		add(b, a);
	}
	if (!n) { // 没有点就把所有边连起来
		printf("%d", m);
	} else {
		for (int i = 1; i <= n; i++) {
			if (!id[i] && d[i]) {
				cnt++;
				dfs(i); // 染出不同的联通块,也可用并查集
			}
		}
		int odd = 0, even = 0, over = 0;
		// 分别代表:奇点数,偶点数,入度超过 2 的点数
		for (int i = 1; i <= n; i++) {
			if (!d[i]) {
				continue;
			}
			if (d[i] & 1) { 
				cnt1[id[i]] = true; // 该块有奇点
				odd++; // 统计奇点
			}
			if (d[i] > 2) {
				cnt2[id[i]] = true; // 该块有入度大于二的点
				over++; // 统计入度大于二的点
			}
		}
		for (int i = 1; i <= cnt; i++) {
			if (!cnt1[i]) {
				if (!cnt2[i]) { // 该联通块仅有入度为 2 的点(是个环)
					even += 2; // 拆环 + 熔合
				} else {
					even++; // 熔合
				}
			}
		}
		int ans = (odd >> 1) + even + over;
		// 奇点两两融合,偶点可能熔合或拆环,入度大于二的点必须熔掉
		printf("%d", ans);
	}
	return 0;
}

(相框里除了某君的遗像外还有 Accepted (heihei))


让我们来解决 『相框』 叭~

Bye bye!!1 👋👋

posted @ 2022-08-12 21:11  北柒kylin  阅读(21)  评论(0编辑  收藏  举报