题目
题目大意
给出一个()个点()条边的图,每条边有个颜色,要么是S
,要么是M
,要求一个该图的生成树,使得两种颜色的边各占一半。
分析
是偶数时无解。
举个例子
原图
原图(红S
,黑M
):
第一步
先用Kruskal把S
边做一个生成树:
第二步
然后在上面的基础上继续Kruskal插入所有可以插入的M
边:
第三步
然后对剩下的M
边,用Kruskal进行破圈:
- 先强制插入该边:
- 然后对之前的答案图跑Kruskal,得到一条现在不能用的
S
边(下图中加粗的边): - 从之前的答案中删除这条
S
边,加入这条M
边:
于是就成功地把一个S
边环成了M
边
用这个操作不断替换,直到达成条件。
代码
#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9') f|=c=='-',c=getchar();
while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
return f?-x:x;
}
#define MAXN 1000
#define MAXM 100000
#define NO return puts("-1"),0
int N,M;
struct Edge{
int u,v,c;
}E[MAXM+5];
struct DSU{
int fa[MAXN+5];
DSU(){}
DSU(int n){
for(int i=1;i<=n;i++)
fa[i]=i;
}
int Find(int x){
return fa[x]==x?x:fa[x]=Find(fa[x]);
}
bool Same(int x,int y){
return Find(x)==Find(y);
}
void Union(int x,int y){
fa[Find(x)]=Find(y);
}
};
set<int> Ans;
int Kruskal(int id){
DSU tmp(N);
tmp.Union(E[id].u,E[id].v);
for(int i:Ans){
int u=E[i].u,v=E[i].v;
if(!tmp.Same(u,v))
tmp.Union(u,v);
else if(E[i].c)
return i;
}
return -1;
}
void Output(bool sign){
printf("%d\n",Ans.size());
for(int i:Ans)
if(sign)
printf("(%d %d %c)\n",E[i].u,E[i].v,E[i].c?'S':'M');
else
printf("%d ",i);
}
int main(){
N=read(),M=read();
for(int i=1;i<=M;i++){
int u=read(),v=read();
char str[10];scanf("%s",str);
E[i].u=u,E[i].v=v,E[i].c=str[0]=='S';
}
if((N-1)&1)
NO;
DSU C(N);
int cnt0=0,cnt1=0,Half=(N-1)/2;
for(int i=1;i<=M;i++){
int u=E[i].u,v=E[i].v;
if(E[i].c&&!C.Same(u,v)){
cnt1++;
C.Union(u,v);
Ans.insert(i);
}
}//第一步
// Output(1);
for(int i=1;i<=M;i++){
if(E[i].c)
continue;
int u=E[i].u,v=E[i].v;
if(!C.Same(u,v)){
cnt0++;
C.Union(u,v);
Ans.insert(i);
}
// Output(1);
}//第二步
for(int i=1;i<=M&&cnt0<Half;i++){
if(E[i].c)
continue;
int u=E[i].u,v=E[i].v;
int id=Kruskal(i);
if(id!=-1){
cnt1--,cnt0++;
Ans.erase(id),
Ans.insert(i);
}
// Output(1);
}//第三步
if(cnt0==Half&&cnt1==Half)
Output(0);
else
puts("-1");
}