失踪人口回归!!可惜回归后第一道题就是水题
题面
给定一张n个点m条边的无向图(不保证联通),其中图上每个边都有颜色(黑白),现在要构造一棵生成树,使树上黑边数量等于k,如果不存在合法的构造方案,输出"no solution"。
k<n≤2×104k<n \le 2\times 10^4k<n≤2×104
m≤105m \le10^5m≤105
题解
首先考虑什么时候会No solotion
- 图不连通
- k太大,比黑边总数还大
- k太小,以至于只用k条黑边无法使图联通
其中第一和第二种都很好判断,至于第三种,简单粗暴的方法是按白边构成的联通块缩点,看缩完的图的点数是否比k小(这里默认图是联通的),但是这个办法太繁琐了,如果熟练克鲁斯卡尔的话,很容易得出第二种方法。
第一遍克鲁斯卡尔,处理出必须连的黑边,即缩完点后的图上的生成树边,具体实现就是先把所有能加进生成树的白边加进去,再来扫黑边,这时加进去的黑边是必须连的。
然后第二遍克鲁斯卡尔,先把必须连的黑边连进去,然后再加入黑边,直到黑边数量达到k,然后加白边,这样同时就完成了构造。
这个思考过程我写的比较短,所以比较精髓,建议慢慢读。
放代码!
# include <bits/stdc++.h>
using namespace std;
namespace fastio{
template<typename tn> void read(tn &a){
tn x=0,f=1;char c=' ';
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar() ) x=x*10+c-'0';
a=x*f;
}
template<typename tn> void print(tn a){
if(a<0) putchar('-'),a=-a;
if(a>9) print(a/10);
putchar(a%10+'0');
}
};
using namespace fastio;
const int N=2e4+5;
const int M=1e5+4;
int n,m,k;
struct Edge{
int x,y;
}e0[M],e1[M],em[M];
int ct0,ct1,ctm,sum,tot;
int f[N];
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
int main(){
read(n),read(m),read(k);
for(int i=1;i<=m;i++){
int x,y;
bool b;
read(x);
read(y);read(b);
if(b==0) e1[++ct1]=(Edge){x,y};
else e0[++ct0]=(Edge){x,y};
}
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=ct0;i++){
int fx=find(e0[i].x);
int fy=find(e0[i].y);
if(fx==fy) continue;
f[fx]=fy;
++sum;
}
for(int i=1;i<=ct1;i++){
int fx=find(e1[i].x);
int fy=find(e1[i].y);
if(fx==fy) continue;
f[fx]=fy;
em[++ctm]=e1[i];
++sum;
}
if(sum<n-1||ctm>k||ct1<k){
puts("no solution");
return 0;
}
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=ctm;i++){
int fx=find(em[i].x);
int fy=find(em[i].y);
printf("%d %d 0\n",em[i].x,em[i].y);
f[fx]=fy;
}
tot=ctm;
for(int i=1;tot<k;i++){
int fx=find(e1[i].x);
int fy=find(e1[i].y);
if(fx==fy) continue;
f[fx]=fy;
printf("%d %d 0\n",e1[i].x,e1[i].y);
tot++;
}
for(int i=1;i<=ct0;i++){
int fx=find(e0[i].x);
int fy=find(e0[i].y);
if(fx==fy) continue;
f[fx]=fy;
printf("%d %d 1\n",e0[i].x,e0[i].y);
}
return 0;
}
话说我时至今日,写到这道题时才幡然大悟:原来cnt是count的意思!