题解 Luogu P3623 [APIO2008]免费道路
题目描述
新亚(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;
}
题解 Luogu P3623 [APIO2008]免费道路的更多相关文章
- 【luogu P3623 [APIO2008]免费道路】 题解
题目链接:https://www.luogu.org/problemnew/show/P3623 说是对克鲁斯卡尔的透彻性理解 正解: 先考虑加入水泥路,然后再考虑加入剩下必须要加入的最少鹅卵石路. ...
- [火星补锅] 水题大战Vol.2 T2 && luogu P3623 [APIO2008]免费道路 题解
前言: 如果我自己写的话,或许能想出来正解,但是多半会因为整不出正确性而弃掉. 解析: 这题算是对Kruskal的熟练运用吧. 要求一颗生成树.也就是说,最后的边数是确定的. 首先我们容易想到一个策略 ...
- P3623 [APIO2008]免费道路
3624: [Apio2008]免费道路 Time Limit: 2 Sec Memory Limit: 128 MBSec Special Judge Submit: 2143 Solved: 88 ...
- [BZOJ3624][Apio2008]免费道路
[BZOJ3624][Apio2008]免费道路 试题描述 输入 输出 输入示例 输出示例 数据规模及约定 见“输入”. 题解 第一步,先尽量加入 c = 1 的边,若未形成一个连通块,则得到必须加入 ...
- [APIO2008]免费道路
[APIO2008]免费道路 BZOJ luogu 先把必须连的鹅卵石路连上,大于k条no solution 什么样的鹅卵石路(u,v)必须连?所有水泥路都连上仍然不能使u,v连通的必须连 补全到k条 ...
- bzoj 3624: [Apio2008]免费道路 生成树的构造
3624: [Apio2008]免费道路 Time Limit: 2 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 111 Solved: 4 ...
- BZOJ 3624: [Apio2008]免费道路
3624: [Apio2008]免费道路 Time Limit: 2 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 1201 Solved: ...
- [Apio2008]免费道路[Kruscal]
3624: [Apio2008]免费道路 Time Limit: 2 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 1292 Solved: ...
- Kruskal算法及其类似原理的应用——【BZOJ 3654】tree&&【BZOJ 3624】[Apio2008]免费道路
首先让我们来介绍Krukal算法,他是一种用来求解最小生成树问题的算法,首先把边按边权排序,然后贪心得从最小开始往大里取,只要那个边的两端点暂时还没有在一个联通块里,我们就把他相连,只要这个图里存在最 ...
随机推荐
- 电子产品使用感受之-- AirPods + Apple Watch S4 = Smart iPod ?
- F#周报2019年第12期
新闻 Amazon.Lambda.RuntimeSupport发布 Forge 3.0架构 Blazor 0.9.0试验版发布 通过微软游戏栈实现更多应用 介绍ASP.NET Core中的gRPC M ...
- 深入理解iostat
前言 iostat算是比较重要的查看块设备运行状态的工具,相信大多数使用Linux的同学都用过这个工具,或者听说过这个工具.但是对于这个工具,引起的误解也是最多的,大多数人对这个工具处于朦朦胧胧的状态 ...
- 为什么vue里面data里面的对象,无法用delete删除属性
因为vue里面的data是用get赋值的,所以无法用delete, 这时你可以用Object.defineProperty() Object.defineProperty(basic,'photo', ...
- 【QT】Installer requires Xcode Version 5.0.0 for Qt download if toolchain not found
When I install QT in MacOS 11.3.6, it prompts I need to install xcode then I install xcode but it wa ...
- locust压测rpc协议
这里主要是google的grpc接口进行压测的一个栗子. Locust是以HTTP为主要目标构建的. 但是,通过编写钩子触发器request_success和 request_failure事件的自定 ...
- UGUI中UI与模型混合显示
法一: 利用Render Texture 在project面板创建 在面板中在创建一个Camera,对准要显示的模型 对Render Texture 进行设置 在Canvas下创建RawImage 就 ...
- 命名空间"xx"已经包含了"xx"的定义
例: namespace A.B { public class C { } } 注:重名的不仅仅是类,还可以结构,枚举,命名空间本身也有可能重复. 这个类C若与命名 ...
- 在vue中使用echarts图表
在vue中使用echarts图表 转载请注明出处:https://www.cnblogs.com/wenjunwei/p/9815290.html 安装vue依赖 使用npm npm instal ...
- Linux内核开发进阶书籍推荐(不适合初学者)
Linux内核开发进阶书籍推荐(不适合初学者) 很早之前就想写一篇文章总结一下Linux Kernel开发的相关资料,项目的原因,再加上家里的一些事情,一直没能找到闲暇,今天终于有些时间,希望可以完成 ...