[CF141E]Clearing Up

122 篇文章 0 订阅

题目

传送门 to luogu

思路

从暴力到正解永远是一条捷径。当然我这种暴力也想不出的就另当别论啦。

我们枚举 n − 1 2 \frac{n-1}{2} 2n1 S S S 边,然后剩下的 M M M 边组成连通块,被 S S S 边串起来。如果不明白,想象成生成树上去掉 S S S 边。就会剩下很多由 M M M 边组成的树。

什么情况下 M M M 边能够组成一颗树?答案是,对于任意一个只由 M M M 边形成的连通块(简称为 M - M\text{-} M-连通块),其点集的子集都可以被连通。

枚举 S S S 也是有讲究的。只有 S S S 边能挑起的大梁——连通两个 M - M\text- M-连通块。所以我们要先用一些 S S S 边将 M M M 连通块全部串起来。注意:两个 M M M 连通块之间只需要一条路。因为这里我们只需要找出 必要 S S S,等会儿还会加入更多 S S S 边。

如果 S S S 边还不够呢?答案是,可以随便选,因为无非是让 M M M 边的负责点减少。而前文已言,点集的子集仍然可以满足条件。举个栗子:

栗子的图

显然 ⟨ 1 , 2 ⟩ , ⟨ 2 , 3 ⟩ , ⟨ 3 , 5 ⟩ \langle 1,2\rangle,\langle 2,3\rangle,\langle 3,5\rangle 1,2,2,3,3,5 都是必选,因为它们连接着两个不同的 M - M\text{-} M-连通块,而 ⟨ 7 , 8 ⟩ \langle 7,8\rangle 7,8 则不是。假如只选这三个 S S S 边,我们需要做到的是,用 M M M 边构建 { 5 , 6 , 7 , 8 , 9 } \{5,6,7,8,9\} {5,6,7,8,9} 的生成树。当然,也有 { 3 , 4 } \{3,4\} {3,4},不过不去研究它。

但是此时 S S S 边还不够。那么我们选上 ⟨ 7 , 8 ⟩ \langle 7,8\rangle 7,8 。之后,需要做到的是,用 M M M 边构建 { 5 , 6 , 7 , 9 } \{5,6,7,9\} {5,6,7,9} 的生成树。

看到了吗?无非就是需要求出的 M M M 边生成树的点集变小了。而子集仍能满足要求,所以 S S S 边可以乱加。

话说 S S S 边连接两个不同的 M - M\text- M-连通块 呢?是完全类似的。其中一个 KaTeX parse error: Undefined control sequence: \tect at position 2: M\̲t̲e̲c̲t̲-连通块 不管这条边的端点即可。

算法已经出来了。先去掉所有 S S S 边,然后用 S S S 边将所有 M - M\text- M-连通块 串起来。 S S S 接着随便选。最后用 M M M 边兜底。

显然复杂度是 O ( α n + m ) \mathcal O(\alpha n+m) O(αn+m) 的。边不需要排序,全是线性扫描。

代码

实现时,可以用并查集求出 M - M\text- M-连通块,然后继续利用该并查集找到所有 “必要 S S S 边” 。

第二步,并查集清空,开始加入 “必要 S S S 边” 以外的 S S S 边。这里不需要管 M M M 边(上面证明过了),所以只看 S S S 边(包括 “必要 S S S 边” 在内)是否形成环。

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 100005;
int n, m;

namespace UFS{
	int fa[MaxN], rnk[MaxN];
	void init(){
		for(int i=1; i<=n; ++i)
			fa[i] = i, rnk[i] = 1;
	}
	inline int find(int a){
		if(fa[a] != a)
			fa[a] = find(fa[a]);
		return fa[a];
	}
	/** @return Whether link successfully */
	bool combine(int a,int b){
		a = find(a), b = find(b);
		if(a == b) return false;
		if(rnk[a] > rnk[b]) swap(a,b);
		fa[a] = b, rnk[b] += rnk[a];
		return true;
	}
}

struct Edge{
	int a, b; char c;
	void input(){
		a = readint(), b = readint();
		c = getchar();
	}
};
Edge e[MaxN];

bool used[MaxN];
int main(){
	n = readint(), m = readint();
	if((n-1)&1){ // 是奇数
		puts("-1"); return 0;
	}
	for(int i=1; i<=m; ++i)
		e[i].input();
	UFS::init();
	int tot = 0; // M 的数量
	for(int i=1; i<=m; ++i){
		if(e[i].c == 'S') continue;
		if(UFS::combine(e[i].a,e[i].b))
			++ tot; // 加边成功
	}
	int xez = 0; // S 的数量
	for(int i=1; i<=m; ++i){
		if(e[i].c == 'M') continue;
		if(UFS::combine(e[i].a,e[i].b))
			++ xez, used[i] = 1;
	}
	UFS::init();
	for(int i=1; i<=m; ++i) if(used[i])
		UFS::combine(e[i].a,e[i].b);
	for(int i=1; i<=m&&xez<(n-1)/2; ++i){
		if(e[i].c == 'M') continue;
		if(UFS::combine(e[i].a,e[i].b))
			++ xez, used[i] = 1, -- tot;
	}
	if(xez != tot || tot != (n-1)/2){
		puts("-1"); return 0;
	}
	printf("%d\n",n-1);
	for(int i=1; i<=m; ++i)
		if(used[i]) printf("%d ",i);
		else if(e[i].c == 'M')
			if(UFS::combine(e[i].a,e[i].b))
				printf("%d ",i);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值