这是一道极好的图论题,虽然我一开始只会做$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 高速公路收费的更多相关文章

  1. 【IOI 2018】Werewolf 狼人

    虽然作为IOI的Day1T3,但其实不是一道很难的题,或者说这道题其实比较套路吧. 接下来讲解一下这个题的做法: 如果你做过NOI 2018的Day1T1,并且看懂了题面,那你很快就会联想到这道题,因 ...

  2. [HihoCoder] Highway 高速公路问题

    Description In the city, there is a one-way straight highway starts from the northern end, traverses ...

  3. [IOI2018]高速公路收费——二分查找+bfs

    题目链接: IOI2018highway 题目大意:给出一张$n$个点$m$条边的无向图,并给出一对未知的起点和终点,每条边都有两种边权$A$和$B$(每条边的$A$和$B$都分别相同),每次你可以设 ...

  4. 【IOI 2018】Combo 组合动作(模拟,小技巧)

    题目链接 IOI的签到题感觉比NOI的签到题要简单啊,至少NOI同步赛我没有签到成功…… 其实这个题还是挺妙妙的,如果能够从题目出发,利用好限制,应该是可以想到的做法的. 接下来开始讲解具体的做法: ...

  5. 【IOI 2018】Doll 机械娃娃

    我感觉这个题作为Day2T1,有一定的挑战性.为$Rxd$没有完成这道题可惜. 我觉得这道题,如果按照前几个部分分的思路来想,就有可能绕进错误的思路中.因为比如说每个传感器最多只在序列中出现$2$次, ...

  6. UVA 1615 Highway 高速公路 (区间选点)

    题意:在一条线段上选出尽量少的点,使得和所有给出的n个点距离不超过D. 分别计算出每个点在线段的满足条件的区间,然后就转化成了区间选点的问题了,按照右端点排序,相同时按照左端点排序,按照之前的排序一定 ...

  7. [IOI 2018] Werewolf

    [题目链接] https://www.luogu.org/problemnew/show/P4899 [算法]         建出原图的最小/最大生成树的kruskal重构树然后二维数点 时间复杂度 ...

  8. ETC(电子不停车收费系统)的发展演变

    ETC引进中国是在上世纪的90年代中期,当时中国部分经济发达地区的高速公路车流量激增,从而导致了收费口的交通堵塞.高速公路堵车现象时有发生,拥堵严重的路段可能会天天堵,有时候一堵好几天.高速公路管理手 ...

  9. Before NOIP 2018

    目录 总结 刷题 2018 - 9 - 24 2018 - 9 - 25 2018 - 9 - 26 2018 - 9 - 27 2018 - 9 - 28 2018 - 9 - 29 2018 - ...

随机推荐

  1. Arthas Alibaba 开源 Java 诊断工具

    Arthas 用户文档 English Docs Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,A ...

  2. String字符串的方法

    String字符串在Java开发中是我们常用的一种数据类型,同时String字符串也为我们提供了大量的方法.通过一些实例的练习,我们可以对String字符串的方法有一个比较清楚的了解. 有一个字符串S ...

  3. 帝国后端php通用Api接口

    先来看一下api数据接口和那些小程序之间的关系,如下面的描述,百度小程序,微信小程序,轻应用,app像这些我们都称为终端小应用.api提供数据:会为各终端小应用提供统一的数据格式.客户小应用,从api ...

  4. mtr的用法场景

    ---引用自阿里云 mtr (My traceroute)也是几乎所有 Linux 发行版本预装的网络测试工具.他把 ping和 traceroute 的功能并入了同一个工具中,所以功能更强大. mt ...

  5. node 集群与稳定

    node集群搭建好之后,还需要考虑一些细节问题. 性能问题 多个工作进程的存活状态管理 工作进程的平滑重启 配置或者静态数据的动态重新载入 其它细节 1 进程事件 Node子进程对象除了send()方 ...

  6. OGG 跳过事务(转)

    http://blog.chinaunix.net/uid-26190993-id-3434074.html    在OGG运行过程中,通常会因为各种各样的原因导致容灾端的REPLICAT进程ABEN ...

  7. CocoaPods pod install的时候报错:invalid byte sequence in UTF-8 (ArgumentError)解决办法

    CocoaPods pod install的时候报错:invalid byte sequence in UTF-8 (ArgumentError)解决办法: 基本可以确定是Podfile中的内容编码有 ...

  8. 使用sass与compass合并雪碧图(一)

    雪碧图就是很多张小图片合并成一张大图片,以减少HTTP请求,从而提升加载速度.有很多软件可以合并雪碧图,但通常不太容易维护,使用compass生成雪碧图应该算是非常方便的方法了,可以轻松的生成雪碧图, ...

  9. 团队C++作业1

    我的分工 在本次作业中,我负责的是建筑类的完成还有调试运行. 建筑类的完成: 首先我认为建筑类的完成中,建筑类中应该有三个成员,水晶,防御塔,泉水. 水晶在这个里头是要被保护的对象,它的功能有回血还有 ...

  10. MongoDB安装笔记

    2017年11月17日,在Windows Service 2008R2上成功安装MongoDB. 版本:mongodb-win32-x86_64-2008plus-ssl-3.4.6-signed.m ...