[CF141E]Clearing Up

题目

传送门 to luogu

思路

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

我们枚举 n 1 2 \frac{n-1}{2} S S 边,然后剩下的 M M 边组成连通块,被 S S 边串起来。

如果不明白,想象成生成树上去掉 S S 边。就会剩下很多由 M M 边组成的树。

什么情况下 M M 边能够组成一颗树?答案是,对于任意一个只由 M M 边形成的连通块(简称为“ M M 连通块”,下同),其点集的子集都可以被连通。下面有个图,别急着走。看完。

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

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

栗子的图

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

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

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

话说 S S 边连接两个不同的 M M 连通块呢?是完全类似的。其中一个 M M 连通块不管这条边的端点即可。

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

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

代码

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

第二步,并查集清空,开始加入“必要 S S 边”以外的 S S 边。这里不需要管 M M 边(上面证明过了),所以只看 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;
}

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/108200383