题解 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算法,他是一种用来求解最小生成树问题的算法,首先把边按边权排序,然后贪心得从最小开始往大里取,只要那个边的两端点暂时还没有在一个联通块里,我们就把他相连,只要这个图里存在最 ...
随机推荐
- 滴滴出行基于RocketMQ构建企业级消息队列服务的实践
小结: 1. https://mp.weixin.qq.com/s/v6NM3UgX-qTI7yO1QPCJrw 滴滴出行基于RocketMQ构建企业级消息队列服务的实践 原创: 江海挺 阿里巴巴中间 ...
- 查看历史会话等待事件对应的session信息
此处以enq: TX - row lock contention等待时间为例. 查看snap_id对应时间 select to_char(s.startup_time,'dd Mon "at ...
- asp.net core 使用NLog记录日志到txt文件
一.使用VisualStudioCode创建一个webapi项目(也可以是mvc等).一个类库(用于封装记录日志方法,当然如果使用依赖注入到控制台项目,就不需要此类库了). 二.在类库中添加NLog. ...
- OSPF(Open Shortest Path First)
1.概述 路由协议OSPF全称为Open Shortest Path First,也就开放的最短路径优先协议,因为OSPF是由IETF开发的,所以所有厂商都可以用. OSPF的流量使用IP协议号. O ...
- linux架构师之路!
目录 1.shell编程总结 2.python编程总结 3.网络基础总结 4.存储的基本知识 5.linux常用 架构学习目录 1.网站架构 2.架构师技术图谱 3.python之路 4.IBM技术主 ...
- JDK8 Stream操作整理
1,forEach this.quoteItemList.forEach(p -> p.setMode(mode)); 2,获取对话属性,去重后生成集合 List<String> p ...
- 接口测试工具-tamper data
1.火狐浏览器插件 安装:1)打开火狐浏览器-alt键-附加组件-搜索tamper data-安装-重启火狐浏览器-在工具下打开tamper data 使用:start tamper 示例:http: ...
- 接口测试工具-fiddler
1.fiddler拦截修改数据 命令介绍: bpu在请求开始时中断,bpafter在响应到达时中断,bps在特定http状态码时中断,bpv/bpm在特定请求method时中断. 提示:命令输入区域输 ...
- CocosCreator的ToggleGroup组件使用
用了CocosCreator也有一段时间,对ToggleGroup始终没有仔细的学习过,只停留在用过的水平.所以因为认识有限,所以以为ToggleGroup对自定义支持得没那么好,这两天因为项目,再学 ...
- string函数详解(配案例)
多说无益上码~ #include<iostream> #include<algorithm> #include<cmath> #include<cstring ...