题解 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算法,他是一种用来求解最小生成树问题的算法,首先把边按边权排序,然后贪心得从最小开始往大里取,只要那个边的两端点暂时还没有在一个联通块里,我们就把他相连,只要这个图里存在最 ...
随机推荐
- java_基础_abstract抽象关键字
java中,当父类中的某些东西不确定时,可以用abstract关键字将此类变成抽象类(也就是说类并不完整,有些东西要等待子类去实现) 注意事项: 1.抽象类中的抽象方法不能有实体,格式如下 publi ...
- halcon 图片加载和设置XY轴滑动块的先后顺序
//必须先加载图片,然后执行 hWndControl.setGUICompRangeX( new int[]{ XTrackBar.Minimum, XTrackBar.Maximum}, XTrac ...
- React.createClass和extends Component的区别
React.createClass和extends Component的区别主要在于: 语法区别 propType 和 getDefaultProps 状态的区别 this区别 Mixins 语法区别 ...
- 【托业】【跨栏】TEST04
16~20 16 17 18 19 20 REVIEW TEST4
- 使用dbms_profiler收集存储过程每步执行时间
最近和优化团队的专家学到一个很有意义的内置包:dbms_profiler,专门用于分析Oracle存储过程中的各段代码的时间开销情况,从而快速找到性能瓶颈的步骤. 1.sys创建dbms_profil ...
- 使用MyEclipse新建maven项目时报An internal error occurred during: "Retrieving archetypes:". GC overhead limit
前几天在上手maven时,遇到了一个十分头疼的问题,我的myeclipse配置的是自己安装的插件 ,总是报 " An internal error occurred during: &quo ...
- appium 3 跑起来
1. 代码如下: from appium import webdriver capabilitise = { "platformName": "Android" ...
- c++ 程序崩溃生成Dump文件
#include "Windows.h"#include "DbgHelp.h" int GenerateMiniDump(PEXCEPTION_POINTER ...
- TP5新增模块
tp5模块自动生成 1build.php配置要生成的目录 <?php// +----------------------------------------------------------- ...
- 从Redis到Codis移植实践
一. 背景 随着业务的发展,线上Redis的数据越来越多,所以必须考虑扩容的事情了.对于redis的扩容,目前可选的方案有三种:1.client自己做sharding,一般是按key的hash值 ...