题目描述
新亚(New Asia)王国有 N 个村庄,由 M 条道路连接。其中一些道路是鹅卵石路,而其它道路是水泥路。保持道路免费运行需要一大笔费用,并且看上去 王国不可能保持所有道路免费。为此亟待制定一个新的道路维护计划。
国王已决定保持尽可能少的道路免费,但是两个不同的村庄之间都应该一条且仅由一条 且仅由一条免费道路的路径连接。同时,虽然水泥路更适合现代交通的需 要,但国王也认为走在鹅卵石路上是一件有趣的事情。所以,国王决定保持刚好 K 条鹅卵石路免费。
举例来说,假定新亚王国的村庄和道路如图 3(a)所示。如果国王希望保持两 条鹅卵石路免费,那么可以如图 3(b)中那样保持道路(1, 2)、(2, 3)、(3, 4)和(3, 5) 免费。该方案满足了国王的要求,因为:(1)两个村庄之间都有一条由免费道 路组成的路径;(2)免费的道路已尽可能少;(3)方案中刚好有两条鹅卵石道路 (2, 3)和(3, 4)
图 3: (a)新亚王国中村庄和道路的一个示例。实线标注的是水泥路,虚线标注 的是鹅卵石路。(b)一个保持两条鹅卵石路免费的维护方案。图中仅标出了免 费道路。
给定一个关于新亚王国村庄和道路的述以及国王决定保持免费的鹅卵石 道路数目,写一个程序确定是否存在一个道路维护计划以满足国王的要求,如果 存在则任意输出一个方案。
输入输出格式
输入格式:
输入第一行包含三个由空格隔开的整数:
N,村庄的数目(1≤N≤20,000);
M,道路的数目(1≤M≤100,000);
K,国王希望保持免费的鹅卵石道路数目(0≤K≤N - 1)。
此后 M 行述了新亚王国的道路,编号分别为 1 到 M。第(i+1)行述了第 i 条 道路的情况。用 3 个由空格隔开的整数述:
ui 和 vi,为第 i 条道路连接的两个村庄的编号,村庄编号为 1 到 N;
ci,表示第 i 条道路的类型。ci = 0 表示第 i 条道路是鹅卵石路,ci = 1 表 示第 i 条道路是水泥路。
输入数据保证一对村庄之间至多有一条道路连接
输出格式:
如果满足国王要求的道路维护方案不存在,你的程序应该在输出第一行打印 no solution。 否则,你的程序应该输出一个符合要求的道路维护方案,也就是保持免费的 道路列表。按照输入中给定的那样输出免费的道路。如果有多种合法方案,你可 以任意输出一种。
sto 某神犇说过 这道题差不多黄题难度 orz
压缩一下这题的题意。就是求原图的一个生成树,使得这个生成树里有\(k\)条\(0\)边和&n-k-1&条\(1\)边。
中心思路:三遍\(Kruscal\)。
还是非常好写的qwq
为了好理解,接下来我们将鹅卵石路称为\(1\)边,水泥路称为\(0\)边。代码中\(a[]\)是存储1边的数组,\(b[]\)是存储\(0\)边的数组。\(at\)是\(1\)边的条数,\(bt\)是\(0\)边的条数。
\(Kruscal\):第一遍
首先将所有的\(0\)边的两端点加到同一个连通分量中。
像这样 qwq:
for (int i=1;i<=bt;i++)
{
f[find(b[i].u)]=find(b[i].v);
}
就是假设将所有的\(0\)边都连接起来,那么会有哪些点是连通的。
接着跑一遍\(Kruscal\)。
for (int i=1,xx,yy;i<=at;i++)
{
xx=find(a[i].u); yy=find(a[i].v);
if (xx!=yy)
{
f[xx]=yy;
answer[++ans]=a[i];
}
}
这个时候加入的\(1\)边有什么特性呢??
很显然(是的),这个时候加入的\(1\)边必定存在于一个正确答案中。因为少了这些边,图就无法连通。(即图的关键边)
这个时候其实已经找到正确答案的一小部分边啦。于是我们可以开一个新的\(fw[]\)数组,是专门针对最终答案的一个并查集存父亲的数组。
在上面的程序里加一句:
fw[found(a[i].u)]=found(a[i].v);
也就是:
for (int i=1,xx,yy;i<=at;i++)
{
xx=find(a[i].u); yy=find(a[i].v);
if (xx!=yy)
{
f[xx]=yy;
answer[++ans]=a[i];
fw[found(a[i].u)]=found(a[i].v);
}
}
题目中还有提到一点,要判断是否无解。
我们知道我们需要\(k\)条\(1\)边,那么如果关键\(1\)边超过了\(k\)条,那么就无解了。
于是我们加个计数。
int asum=0;
for (int i=1;i<=bt;i++)
{
f[find(b[i].u)]=find(b[i].v);
}
for (int i=1,xx,yy;i<=at;i++)
{
xx=find(a[i].u); yy=find(a[i].v);
if (xx!=yy)
{
asum++;
f[xx]=yy;
answer[++ans]=a[i];
fw[found(a[i].u)]=found(a[i].v);
}
}
if (asum>k)
{
printf("no solution");
return 0;
}
\(Kruscal\):第二遍
和上述道理差不多。同样的方法,找到关键\(0\)边。如果关键\(0\)边条数\(>n-k-1\),无解。
int bsum=0;
for (int i=1;i<=at;i++)
{
f[find(a[i].u)]=find(a[i].v);
}
for (int i=1,xx,yy;i<=bt;i++)
{
xx=find(b[i].u); yy=find(b[i].v);
if (xx!=yy)
{
bsum++;
f[xx]=yy;
answer[++ans]=b[i];
fw[found(b[i].u)]=fw[found(b[i].v)];
}
}
if (bsum>n-k-1)
{
printf("no solution");
return 0;
}
\(Kruscal\):第三遍(&第四遍)
既然已经找完所有的关键边了,那就随便塞边做生成树吧(就是裸的\(Kruscal\))
for (int i=1,xx,yy;i<=at;i++)
{
if (asum==k)
{
break;
}
xx=found(a[i].u); yy=found(a[i].v);
if (xx!=yy)
{
asum++;
fw[xx]=yy;
answer[++ans]=a[i];
}
}
if (asum<k)
{
printf("no solution");
return 0;
}
for (int i=1,xx,yy;i<=bt;i++)
{
if (bsum==n-k-1)
{
break;
}
xx=found(b[i].u); yy=found(b[i].v);
if (xx!=yy)
{
bsum++;
fw[xx]=yy;
answer[++ans]=b[i];
}
}
if (bsum<n-k-1)
{
printf("no solution");
return 0;
}
加个无解判断。如果\(1\)边或者\(0\)边没有达到目标数量(\(k\)和\(n-k-1\)),那么无解。
完整程序
(如果码风不适请见谅qwq)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
int n,m,k;
struct qwq
{
int u,v,w;
}a[233333],b[233333];
int at=0,bt=0;
int f[23333],fw[23333];
void Init()
{
for (int i=1;i<=n;i++)
{
f[i]=i;
}
}
void INIT()
{
for (int i=1;i<=n;i++)
{
fw[i]=i;
}
}
int find(int q)
{
if (f[q]==q) return q;
return f[q]=find(f[q]);
}
int found(int q)
{
if (fw[q]==q) return q;
return fw[q]=found(fw[q]);
}
qwq answer[233333];
int ans=0;
void die()
{
printf("no solution");
exit(0);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
qwq qwqvqwqvqwq;
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&qwqvqwqvqwq.u,&qwqvqwqvqwq.v,&qwqvqwqvqwq.w);
if (qwqvqwqvqwq.w)
{
b[++bt]=qwqvqwqvqwq;
}
else
{
a[++at]=qwqvqwqvqwq;
}
}
Init();
INIT();
int asum=0;
for (int i=1;i<=bt;i++)
{
f[find(b[i].u)]=find(b[i].v);
}
for (int i=1,xx,yy;i<=at;i++)
{
xx=find(a[i].u); yy=find(a[i].v);
if (xx!=yy)
{
asum++;
f[xx]=yy;
answer[++ans]=a[i];
fw[found(a[i].u)]=found(a[i].v);
}
}
if (asum>k)
{
die();
}
Init();
int bsum=0;
for (int i=1;i<=at;i++)
{
f[find(a[i].u)]=find(a[i].v);
}
for (int i=1,xx,yy;i<=bt;i++)
{
xx=find(b[i].u); yy=find(b[i].v);
if (xx!=yy)
{
bsum++;
f[xx]=yy;
answer[++ans]=b[i];
fw[found(b[i].u)]=fw[found(b[i].v)];
}
}
if (bsum>n-k-1)
{
die();
}
for (int i=1,xx,yy;i<=at;i++)
{
if (asum==k)
{
break;
}
xx=found(a[i].u); yy=found(a[i].v);
if (xx!=yy)
{
asum++;
fw[xx]=yy;
answer[++ans]=a[i];
}
}
if (asum<k)
{
die();
}
for (int i=1,xx,yy;i<=bt;i++)
{
if (bsum==n-k-1)
{
break;
}
xx=found(b[i].u); yy=found(b[i].v);
if (xx!=yy)
{
bsum++;
fw[xx]=yy;
answer[++ans]=b[i];
}
}
if (bsum<n-k-1)
{
die();
}
for (int i=1;i<=ans;i++)
{
printf("%d %d %d\n",answer[i].u,answer[i].v,answer[i].w);
}
return 0;
}