题目
思路
从暴力到正解永远是一条捷径。当然我这种暴力也想不出的就另当别论啦。
我们枚举 n − 1 2 \frac{n-1}{2} 2n−1 条 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;
}