题目
思路
从暴力到正解永远是一条捷径。当然我这种暴力也想不出的就另当别论啦。
我们枚举 条 边,然后剩下的 边组成连通块,被 边串起来。
如果不明白,想象成生成树上去掉 边。就会剩下很多由 边组成的树。
什么情况下
边能够组成一颗树?答案是,对于任意一个只由
边形成的连通块(简称为“
连通块”,下同),其点集的子集都可以被连通。下面有个图,别急着走。看完。
枚举 也是有讲究的。只有 边能挑起的大梁——连通两个 连通块。所以我们要先用一些 边将 连通块全部串起来。注意:两个 连通块之间只需要一条路。因为这里我们只需要找出“必要 边”,等会儿还会加入更多 边。
如果 边还不够呢?答案是,可以随便选,因为无非是让 边的负责点减少。而前文已言,点集的子集仍然可以满足条件。举个栗子:
显然 都是必选,因为它们连接着两个不同的 连通块,而 则不是。假如只选这三个 边,我们需要做到的是,用 边构建 的生成树。当然,也有 ,不过不去研究它。
但是此时 边还不够。那么我们选上 。之后,需要做到的是,用 边构建 的生成树。
看到了吗?无非就是需要求出的 边生成树的点集变小了。而子集仍能满足要求,所以 边可以乱加。
话说 边连接两个不同的 连通块呢?是完全类似的。其中一个 连通块不管这条边的端点即可。
算法已经出来了。先去掉所有 边,然后用 边将所有 连通块串起来。 接着随便选。最后用 边兜底。
显然复杂度是 的。边不需要排序,全是线性扫描。
代码
实现时,可以用并查集求出 连通块,然后继续利用该并查集找到所有“必要 边”。
第二步,并查集清空,开始加入“必要 边”以外的 边。这里不需要管 边(上面证明过了),所以只看 边(包括“必要 边”在内)是否形成环。
#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;
}