【IOI 2018】Highway 高速公路收费
这是一道极好的图论题,虽然我一开始只会做$18$分,后来会做$51$分,看着题解想了好久才会做(吐槽官方题解:永远只有一句话),但这的确是一道好题,值得思考,也能启发思维。
如果要讲这道题,还是要从部分分一点一点讲起,毕竟解题时的思路也是慢慢这么推进的。
首先第一次把所有边都变成同一种颜色,询问可以得到$s$到$t$的无权最短路径的长度$dist$。这个询问是必须的,因为这个$dist$在接下来的判定中起来很大的作用。
$Subtask 2$:给定一棵树,知道$s$是根,求$t$:
这个问题将作为子问题多次出现在后续的问题中。由于询问次数的限制,猜想得到答案的方法是二分,事实上也是如此。
思考我们该在什么上二分呢?我们可以在$Bfs$序上二分。
具体来讲,如果二分到区间$[l,r]$时,我们需要判定的就是$t$会不会存在于$[l, mid]$中。假设一开始所有树边都是$B$,我们把一条边染成$A$当且仅当它是某一个在$Bfs$序的前缀$[1,mid]$中出现过的点的祖先链上。这样,可以发现,所有在$[1,mid]$里的点到根的距离都是完全被$A$覆盖的,而$[mid + 1, n]$里的点到根的距离至少存在一条$B$边。所以我们可以通过$ask$返回值是否为$dist * A$来判定二分。
有一个小问题,在这个子问题中我们把$A$和$B$互换是不会产生什么问题的,因为树上的最短路径是唯一的。虽然这个看起来十分显然,但可能就在后面的任务中引发问题,先留作读者思考。
如果需要解决这个子问题,你需要$log_{2}n = 17$次询问。
$Subtask 4$:给定一棵树,找出$s$和$t$:
这个子任务的形式已经和最终的任务很像了,只不过一个是树,一个是图。考虑到我们能不能把这个问题转化成已知的问题。
如果可以找到一条边$e=u -> v$,使得$e$一定在$s$到$t$的路径上。那我们就可以割掉$e$,然后分成两棵树,在$u$为根的树里找$s$,在$v$为根的树里找$t$。并且如果当前在$u$的树里找$s$,则需要把$e$和$v$为根的树里的所有边都赋成$A$,就能和$Subtask 2$一样做了。
如果需要解决这个子问题,你需要$log_{2}m + 2 log_{2}\frac{n}{2} = 17+16+16=49$次询问。
$Subtask 6$:给定一张图,找出$s$和$t$:
这里同样考虑我们如果可以找到一条边$e=u->v$,使得$e$至少在$s$到$t$的一条最短路上(不带权,以下的最短路皆指不带权)。这里考虑到图上的最短路可能有多条,会比树上稍麻烦一点。这里详细介绍一下具体怎么找到这条边,因为在$Subtask 4$中也有类似的问题。我们把$m$条边列到一个序列中,同样用二分来解决。假设一开始所有边都是$A$,我们知道可行的$e$一定会在序列的$[l,r]$中出现至少一条,$s->t$的最短路仍然是$dist*A$,判定$e$会不会在$[l,mid]$之间存在:我们先把$[l,mid]$的边染成$B$,询问$s$到$t$的最短路,如果最短路不再是$dist*A$,说明原先图中没有被$B$覆盖任何一条边的一条从$s->t$的最短路上,现在被$[l,mid]$中至少有一条边覆盖了,说明$[l,mid]$中至少存在一条可行的$e$,于是把$[l,mid]$染回$A$,在$[l,mid]$中二分;如果询问到的最短路仍是$dist * A$,说明至少存在一条最短路还全部由$A$覆盖,答案会存在于$[mid + 1,r]$中。这个想法的出发点,主要是每次缩小二分范围时,始终保证存在一条最短路还全部都由$A$覆盖。
现在我们找到的这条$u->v$的边了,假设这条路是$s->u->v->t$的,那么可得$s$到$u$的距离一定严格小于到$v$的距离,$t$到$v$的距离严格小于到$u$的距离,请读者自行证明。那我们可以分别从$u,v$两点$Bfs$一遍,得到的$s$的候选点集$S$和$t$的候选点集$T$是不相交的,并且我们得到了两棵$Bfs$树。当我们把不在$Bfs$树上的边全都染成$B$,树边染成$A$时,我们发现问题已经转化成了$Subtask4$的样子。事实上,我们接着按照$Subtask4$的方法去做,就能完整地解决这个问题了。
但是这里我还是想要讲一下细节上存在的问题,也就是染$A$染$B$交换什么时候会出问题。在树上做的时候完全不会有问题,我前面说了,因为树上的最短路径是唯一的。但图上我们始终坚持保留一条全$A$的路径,也就是让判定条件是$dist*A$,原因很简单,如果不这么做会导致另一条不一定是最短路的路径成为当前染色情况下的带权最短路。
举一个在找$e=u->v$时的例子,假设$A<<B$,$s$到$t$有两条长为$2,3$的路径,一开始所有边都是$B$:如果我们第一次二分把长度为$3$的路径染为$A$,得到的回答会是$3 * A$,也就是走了长度为$3$的路,我们无法从中知道这些染成$A$中的边中是否一定存在最短路上的边。
以及在$Bfs$树上二分的时候,如果调换$A,B$的角色,只把树边染成$B$,每次二分的时候把树上的二分范围以外的边染成$A$,问题就会和上个例子类似。实际结果就是询问得到的最短路有可能是从横跨两棵树的边上绕过去的,而并没有经过$u->v$,因为一个点可能从它的子树中绕出去会更短,具体例子在这里就不做说明了。
于是来说明一下本题的询问的次数:
最开始需要$1$次询问,得到最短路径长度$dist$。
找出$e=u->v$的复杂度是$log_{2}m$的,最坏需要$17$次。
$e$把所有点都分成两棵树,分别二分,最坏情况下两棵树大小相同,为$\frac{n}{2}$,那需要$2log_{2}\frac{n}{2}=32$次。
总共需要$50$次询问,正好卡到题目限制的上界。事实上,这个算法的询问次数很难被卡到正好$50$次,至少官方数据没有。
至于时间复杂度,$O(nlogn)$想必是没有什么问题的。
$\bigodot$技巧&套路:
- $Bfs$树的利用
- 交互题中二分判定准则的选定
- 未知问题向已知的子问题的转化
- 严谨的证明与细心的考虑
#include "highway.h"
#include <queue>
#include <algorithm> using namespace std; typedef long long LL;
const int N = ; int n, m, a, b, dist, ide;
int dis_u[N], dis_v[N], bo[N], fu[N], fv[N];
vector<int> u, v, qr, gu, gv;
vector<pair<int, int> > g[N]; int Search_edge() {
fill(qr.begin(), qr.end(), );
dist = ask(qr) / a;
int l = , r = m - ;
for (int md; l < r; ) {
md = (l + r) >> ;
fill(qr.begin() + l, qr.begin() + md + , );
if (ask(qr) == (LL)dist * a) {
l = md + ;
} else {
fill(qr.begin() + l, qr.begin() + md + , );
r = md;
}
}
return l;
} void Bfs(int s, int *dis, int *fa) {
static queue<int> Q;
fill(dis, dis + n, -);
Q.push(s), dis[s] = ;
for (int x; !Q.empty(); ) {
x = Q.front(), Q.pop();
for (auto p : g[x]) {
if (dis[p.first] == -) {
dis[p.first] = dis[x] + ;
fa[p.first] = p.second;
Q.push(p.first);
}
}
}
} int Solve(vector<int> &gr, vector<int> &gf, int book, int *fr, int *ff) {
int l = , r = gr.size() - ;
for (int md; l < r; ) {
md = (l + r) >> ;
fill(qr.begin(), qr.end(), );
qr[ide] = ;
for (int i = ; i < gf.size(); ++i) qr[ff[gf[i]]] = ;
for (int i = ; i <= md; ++i) qr[fr[gr[i]]] = ;
if (ask(qr) != (LL)dist * a) {
l = md + ;
} else {
r = md;
}
}
return gr[l];
} void find_pair(int _n, vector<int> _u, vector<int> _v, int _a, int _b) {
n = _n, a = _a, b = _b;
u = _u, v = _v;
m = u.size(), qr.resize(m);
ide = Search_edge();
for (int i = ; i < m; ++i) {
g[u[i]].emplace_back(v[i], i);
g[v[i]].emplace_back(u[i], i);
} Bfs(u[ide], dis_u, fu);
Bfs(v[ide], dis_v, fv);
for (int i = ; i < n; ++i) {
if (dis_u[i] < dis_v[i]) gu.push_back(i), bo[i] = -;
if (dis_u[i] > dis_v[i]) gv.push_back(i), bo[i] = ;
}
sort(gu.begin(), gu.end(), [](int x, int y) { return dis_u[x] < dis_u[y]; });
sort(gv.begin(), gv.end(), [](int x, int y) { return dis_v[x] < dis_v[y]; });
int as = Solve(gu, gv, -, fu, fv);
int at = Solve(gv, gu, , fv, fu);
answer(as, at);
}
【IOI 2018】Highway 高速公路收费的更多相关文章
- 【IOI 2018】Werewolf 狼人
虽然作为IOI的Day1T3,但其实不是一道很难的题,或者说这道题其实比较套路吧. 接下来讲解一下这个题的做法: 如果你做过NOI 2018的Day1T1,并且看懂了题面,那你很快就会联想到这道题,因 ...
- [HihoCoder] Highway 高速公路问题
Description In the city, there is a one-way straight highway starts from the northern end, traverses ...
- [IOI2018]高速公路收费——二分查找+bfs
题目链接: IOI2018highway 题目大意:给出一张$n$个点$m$条边的无向图,并给出一对未知的起点和终点,每条边都有两种边权$A$和$B$(每条边的$A$和$B$都分别相同),每次你可以设 ...
- 【IOI 2018】Combo 组合动作(模拟,小技巧)
题目链接 IOI的签到题感觉比NOI的签到题要简单啊,至少NOI同步赛我没有签到成功…… 其实这个题还是挺妙妙的,如果能够从题目出发,利用好限制,应该是可以想到的做法的. 接下来开始讲解具体的做法: ...
- 【IOI 2018】Doll 机械娃娃
我感觉这个题作为Day2T1,有一定的挑战性.为$Rxd$没有完成这道题可惜. 我觉得这道题,如果按照前几个部分分的思路来想,就有可能绕进错误的思路中.因为比如说每个传感器最多只在序列中出现$2$次, ...
- UVA 1615 Highway 高速公路 (区间选点)
题意:在一条线段上选出尽量少的点,使得和所有给出的n个点距离不超过D. 分别计算出每个点在线段的满足条件的区间,然后就转化成了区间选点的问题了,按照右端点排序,相同时按照左端点排序,按照之前的排序一定 ...
- [IOI 2018] Werewolf
[题目链接] https://www.luogu.org/problemnew/show/P4899 [算法] 建出原图的最小/最大生成树的kruskal重构树然后二维数点 时间复杂度 ...
- ETC(电子不停车收费系统)的发展演变
ETC引进中国是在上世纪的90年代中期,当时中国部分经济发达地区的高速公路车流量激增,从而导致了收费口的交通堵塞.高速公路堵车现象时有发生,拥堵严重的路段可能会天天堵,有时候一堵好几天.高速公路管理手 ...
- Before NOIP 2018
目录 总结 刷题 2018 - 9 - 24 2018 - 9 - 25 2018 - 9 - 26 2018 - 9 - 27 2018 - 9 - 28 2018 - 9 - 29 2018 - ...
随机推荐
- eclipse以MapReduce本地模式运行程序
1.准备好所需的文件winutils.exe.hadoop-eclipse-plugin-2.7.3.jar.hadoop-common-2.2.0-bin-master.zip 下载路径:http: ...
- python常用模块目录
博客目录总纲首页 python其他知识目录 random hashlib os sys json __name__ shutil xlrd xlwt xlutils 核心模块:os s ...
- iOS 源代码混淆(初步混淆)
我们可以用classdump对原程序进行 dump,像上篇文章( Class-dump 安装和使用记录(导出应用的头文件)),我们可以看到所有.h 文件全暴露了(如下图) 点击HWAccount.h后 ...
- 渐入OO课的深处,探索多线程的秘密——OO第二次博客总结
一次又一次的挑战,一次又一次全新的知识,我来到了多线程的面前 第五次作业 1.度量分析 >第五次作业由于很大程度上调用的是前两次电梯的一些代码,所以存在的问题与前几次也十分相似.同时由于第一次使 ...
- Bag类课后作业
20162316 Bag课后作业 下面小标题都是码云链接 实现代码 import java.util.Arrays; public class Bag implements BagInterface ...
- bata6
目录 组员情况 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:恺琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示组内最新成果 团队签入记 ...
- iOS- 利用AFNetworking3.0+(最新AFN) - 实现文件上传
官方建议AFN的使用方法 0.导入框架准备工作 •1. 将AFNetworking3.0+框架程序拖拽进项目 •2. 或使用Cocopod 导入AFNetworking3.0+ •3. 引入 ...
- Struts1简单开发流程梳理
共享数据的4种范围MVC设计模式JSP model1.JSP model2struts实现MVC机制(ActionServlet.Action)struts-config.xml ActionServ ...
- Beta阶段DAY1
一.提供当天站立式会议照片一张 二.每个人的工作 1.讨论项目每个成员的昨天进展 刘阳航:了解了自己再beta阶段的安排,但因为考试,有心无力. 林庭亦:颜色设置的相关代码的查看. 郑子熙:和另一位组 ...
- Selenium_IDE的基本使用及脚本解析
Selenium确实还是很强大的.根据我以往的经验,这个东西在web测试里的作用还是相当大的.经过近期研究,暂时对基本运作方式有了一定了 解,依旧找个实例记录一下.本段脚本实现的是网易返现个人中心登录 ...