这是一道极好的图论题,虽然我一开始只会做$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. c++三大概念要分清--重载,隐藏(重定义),覆盖(重写)

    重载,隐藏(重定义),覆盖(重写)—这几个名词看着好像很像,不过其实一样都不一样!! 综述: 说明:覆盖中的访问修饰符可以不同是指可以不用显示地用virtual:当访问修饰符改为const或者stat ...

  2. Codeforces70 | Codeforces Beta Round #64 | 瞎讲报告

    目录 前言 正文 A B C D E 前言 这个毒瘤的517 放了Div1 然后D题是昨天讲的动态凸包(啊喂!我还没来的及去写 结果自己想的是二分凸包 (当然没有写出来 写完前两题之后就愉快地弃疗 C ...

  3. MyBatis思维导图

    1.初识框架技术 2.搭建MyBatis环境 3.掌握MyBatis的核心API 4.掌握MyBatis的核心配置文件:主要用于配置数据库连接和MyBatis运行时所需的各种特性 5.掌握SQL映射文 ...

  4. basename命令详解

    基础命令学习目录首页 摘要:前言bashname命令用于获取路径中的文件名或路径名(获取的时候叶子节点的元素内容)常见用法举例basenamepath获取末尾的文件名或路径名1:[aliyunzixu ...

  5. numastat命令详解

    基础命令学习目录 作者:[吴业亮]博客:http://blog.csdn.net/wylfengyujiancheng一.系统架构的演进从SMP到NUMA1.SMP(Symmetric Multi-P ...

  6. React 之容器组件和展示组件相分离解密

    Redux 的 React 绑定库包含了 容器组件和展示组件相分离 的开发思想.明智的做法是只在最顶层组件(如路由操作)里使用 Redux.其余内部组件仅仅是展示性的,所有数据都通过 props 传入 ...

  7. 第二阶段Sprint4

    昨天:事实现保存到指定路径,并能够选择播放 今天:放弃改文件名,自动生成,实现暂停后继续录制 遇到的问题:不知道为什么实现不了,不然之前录制的视频就会丢失重新录

  8. sampleFactory(女娲造人)

    使用简单工厂模式模拟女娲(Nvwa)造人(Person),如果传入参数M,则返回一个Man对象,如果传入参数W,则返回一个Woman对象,如果传入参数R,则返回一个Robot对象. package c ...

  9. 《UML大战需求分析》-读后感一

    UML英文全拼是unified modeling language 就是统一建模语言. UML就是一种软件开发中帮助我们设计的标准,虽然说是建模语言但是它是图形,图形能更清楚的表达我们对软件的想法.U ...

  10. Codeforces Round #196 (Div. 2) D. Book of Evil 树形dp

    题目链接: http://codeforces.com/problemset/problem/337/D D. Book of Evil time limit per test2 secondsmem ...