题解 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算法,他是一种用来求解最小生成树问题的算法,首先把边按边权排序,然后贪心得从最小开始往大里取,只要那个边的两端点暂时还没有在一个联通块里,我们就把他相连,只要这个图里存在最 ...
随机推荐
- jQuery地图插件jVectorMap的简单使用
1.官网下载jVectorMap插件压缩文件 官网地址:http://www.jvectormap.com/ 2.解压文件包括jVectorMap库及基础样式表,编写Html文件,引入jQuery框架 ...
- 2.2String工具类
1:split方法 public class SplitDemo1 { public static String[] name = new String[20]; public SplitDemo1( ...
- VUE错误码Attribute ':sizeOpts' must be hyphenated
Attribute ':sizeOpts' must be hyphenated 因为属性有大写,需要添加 - 来取代 例如 tampData 换成 tamp-data 就可以了
- Autofac之依赖注入
这里主要学习一下Autofac的依赖注入方式 默认构造函数注入 class A { public B _b; public A() { } public A(B b) { this._b = b; } ...
- P1368 工艺 SA/最小表示法
正解:SA/最小表示法 解题报告: 传送门! 听说正解是最小表示法,,,O(n)然后常数还挺小的,,, 但是我不会QAQ! 所以先写下SA的做法趴,,,等get了最小表示法再来写正解QAQ 就这种题算 ...
- AD、PADS、Cadence对比
本人平时主要接触的是FPGA设计,最近找工作发现有些企业要求会画PCB电路,所以开始学习相关工具软件.主流软件是Altium Designer,PADS和Cadence这三个. 三大工具的用途: AD ...
- 深浅copy
浅拷贝 只copy了一层 可哈希的变量改变则二者不同 不可哈希变量改变二者都改变深拷贝 全部都copy了 不论什么值改变 原值都改变呢 变量赋值.浅拷贝.深拷贝 1,创建变量 1.1不可变对象 ...
- JS获取页面复选框选中的值
function jqchk(){ //jquery获取复选框值 var chk_value =[]; $('input[class="sel"]:checked').each(f ...
- 2018-2019-2 《网络对抗技术》Exp0 Kali安装 Week1 20165321
安装kali 在vm里面新建虚拟机,选择典型 选择安装程序光盘镜像文件,系统出现无法检测此光盘镜像中的操作系统 虚拟机命名选择安装位置 给虚拟机分配的磁盘大小 点击自定义硬件,更改虚拟机硬件 选择Gr ...
- Solaris环境下使用snoop命令抓包
(1)报文抓取 Solaris中自带有snoop抓包工具,通过执行相应的命令抓取. 抓取目的地址为10.8.3.250的数据包,并存放到/opt/cap250的文件里 snoop -o /opt/ca ...