IOI 2023 最长路程 题解

First Post:

Last Update:

magic!

下文用 表示 点与 点有直接连边。

首先注意到 时问题不弱于无向图哈密顿路,所以要找最长肯定需要用到 带来的一些性质。

容易发现整张图的连通块个数不超过

考虑维护两条链 ,容易发现,加入一个点 之后,整个图仍然可以被划分成两条不自交的链(我也不知道这个 idea 怎么能够被想出来,可能这样可以契合三个点的限制?反正我没有想出来)。

具体地,设 的末尾点为 的末尾点为 ,那么如果 ,就将 加入对应的链尾,否则一定有 ,此时把 倒序接到 的末尾,让 成为新的 即可。

接下来,我们用 次询问判断 的起点、终点之间分别有无连边,如果有的话,显然可以将 拼接起来,形成原图的一条哈密顿路,否则,由 的限制, 的起点与终点, 的起点与终点之间,肯定都有边,也就是说, 在原图上是一个环。

因此,如果 中任意点与 中任意点有连边,显然也可以将这两个环拼成哈密顿路,否则输出大小较大的哪个环即可。

如何找到 之间的连边?可以进行一次二分,二分时检查 有没有边。二分后就能找到这条边在 中的端点 ,然后再在 上二分,检查 的一个前缀是否与 有边。这样就能找到有直接连接的两个点

上述做法的询问数上界为 ,可以通过 的限制,拿到 分的高分。

容易发现瓶颈在于维护 时每个点都进行了两次操作。通过做交互题的一些经验,我们容易想到尝试用三次询问来确定两个点。

在加入 时,进行如下过程:

  1. 如果
    1. 如果 ,令 。(这里 号表示拼接)
    2. 如果 ,令
    3. 此时一定有 ,令 (这里 表示 翻转后的结果)
  2. 否则,如果
    1. 如果 ,令
    2. 否则,一定有 ,令
  3. 否则,一定有
    1. 如果 ,令
    2. 否则,一定有 ,令

于是询问上界变为

完全想不到这种题目啊。/hanx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <bits/stdc++.h>
#include "longesttrip.h"
using namespace std;
const int N = 3e2 + 5;
int n,D;
vector<int> L1,L2;
bool are_connected(int x,int y) {
vector<int> A(1,x),B(1,y);
return are_connected(A,B);
}
vector<int> longest_trip(int _n,int _d) {
n = _n;D = _d;L1.clear();L2.clear();
auto Add2 = [&](const int &C,const int &D) {
int A = L1.back(),B = L2.back();
// printf("A,B,C,D:%d,%d,%d,%d\n",A,B,C,D);
if(are_connected(C,D)) {
if(are_connected(A,C)) { L1.push_back(C);L1.push_back(D);return;}
if(are_connected(B,C)) { L2.push_back(C);L2.push_back(D);return;}
while(L2.size()) L1.push_back(L2.back()),L2.pop_back();
L2.push_back(C);L2.push_back(D);return;
}
if(are_connected(A,C)) {
if(are_connected(B,D)) { L1.push_back(C);L2.push_back(D);return;}
L1.push_back(C); while(L2.size()) L1.push_back(L2.back()),L2.pop_back();
L2.push_back(D);
return;
}
if(are_connected(B,C)) {
L1.push_back(D);L2.push_back(C);
return;
}
L1.push_back(D); while(L2.size()) L1.push_back(L2.back()),L2.pop_back();
L2.push_back(C);
};
for(int i = 0;i < n;i++) {
if(L1.empty()) {L1.push_back(i);continue;}
if(L2.empty()) {L2.push_back(i);continue;}
if(i + 1 < n) { Add2(i,i + 1);++i;continue;}
if(are_connected(L1.back(),i)) { L1.push_back(i);continue;}
if(are_connected(L2.back(),i)) { L2.push_back(i);continue;}
while(L2.size()) L1.push_back(L2.back()),L2.pop_back();
L2.push_back(i);
}
auto Chk0 = [&]() {
if(are_connected(L1.back(),L2.front())) return true;
return false;
};
auto Mge = [&]() {
auto res = L1;
for(auto i : L2) res.push_back(i);
return res;
};
if(Chk0()) return Mge();
reverse(L2.begin(),L2.end());
if(Chk0()) return Mge();
swap(L1,L2);
if(Chk0()) return Mge();
reverse(L1.begin(),L1.end());
if(Chk0()) return Mge();
int c1 = 0,c2 = 0;
int lef = 0,rig = L1.size();
while(lef < rig) {
int mid = lef + rig >> 1;
vector<int> pre;
for(int i = 0;i <= mid;i++) pre.push_back(L1[i]);
if(are_connected(pre,L2)) rig = mid;
else lef = mid + 1;
}
c1 = lef;
if(c1 == L1.size()) {
return L1.size() > L2.size() ? L1 : L2;
}
lef = 0,rig = L2.size() - 1;
while(lef < rig) {
int mid = lef + rig >> 1;
vector<int> pre,nd(1,L1[c1]);
for(int i = 0;i <= mid;i++) pre.push_back(L2[i]);
if(are_connected(pre,nd)) rig = mid;
else lef = mid + 1;
}
c2 = lef;
vector<int> res;

for(int i = (c1 + 1) % L1.size();i != c1;i = (i + 1) % L1.size())
res.push_back(L1[i]);
res.push_back(L1[c1]);
res.push_back(L2[c2]);
for(int i = (c2 + 1) % L2.size();i != c2;i = (i + 1) % L2.size())
res.push_back(L2[i]);
return res;
}