JOISC 2017 题解
Posted LarsWerner
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JOISC 2017 题解相关的知识,希望对你有一定的参考价值。
JOISC2017 Day1 开荒者 Cultivation
首先进行转化,转化为对于每个点 \\(x,y\\),将其扩成一个左上角为 \\((x-a,y-c)\\) 右下角为 \\((x+b,y+d)\\) 的矩形后覆盖整个 \\(R\\times C\\) 的大举行。首先考虑枚举 \\(a,b\\),那么我们可以得到平面上的几条垂直线段,那么我们可以得到一些关于 \\(c,d\\) 的不等式。对于每行,令其最左侧穿过的线段到第一列的距离为 \\(f\\),最右侧穿过线段到最后一列的距离为 \\(g\\),相邻两条穿过的线段最大间距为 \\(h\\),那么需要 \\(c\\ge f\\),\\(d\\ge g\\),\\(c+d\\ge h\\)。也就是分别确定了最大的 \\(f,g,h\\) 就可以确定出最小的 \\(c+d\\)。
但是这样复杂度是不可接受的。我们发现 \\(a+b\\) 不变的情况下,\\(a\\) 的变化只会导致 \\(f,g,h\\) 发生一些平移,不会改变值。我们可以直接枚举 \\(a+b\\),然后找到一个长度为 \\(R\\) 的区间,然后分别取区间内最大的 \\(f,g,h\\),这个东西可以单调队列维护。同时容易发现可能的 \\(a+b\\) 只有 \\(O(n^2)\\) 种(\\(x_i-x_j\\) 和 \\(C-x_i+x_j-1\\))。同时我们发现对于一种 \\(a+b=k\\),它每一行所穿过的线段对应的点一定是将原先的所有点按 \\(x\\) 排序后的一段区间,所以我们提前预处理出区间的 \\(f,g,h\\) 即可 \\(O(n^2\\log n)\\) 预处理,\\(O(n^3)\\) 求解。
https://qoj.ac/submission/108987
JOISC2017 Day1 港口设施 Port Facility
首先考虑使用 01 并查集。考虑扫描线,扫的时候扫到左端点就将这个点加入中,然后右端点就把仍然存在的且左端点位于 \\([l_i+1,r_i-1]\\) 的点和它进行连边(0连1,1连0),再把这个点给删掉。考虑把这个操作放到线段树上去,每次若这个区间中有点就对这个区间打上 tag,合并 tag 时只需要两个 tag 的点 0连0,1连1 地连边。每次删点的时候再把所有有关这个点的 tag 给下放。复杂度 \\(O(n\\cdot \\log n\\cdot\\alpha(n))\\)。
实际上有更简单的解法:还是考虑那样连边,但是可以均摊暴力把每次要和当前点连边的点给合并了。这样就不需要线段树维护了。
https://qoj.ac/submission/108520
JOISC2017 Day1 手持花火 Sparklers
先二分 \\(v\\),然后把 \\(v\\) 乘到 \\(T\\) 上变成 \\(v=1\\),然后再 \\(a\\times 2\\),\\(T\\times 2\\) 规避分数。然后考虑最优情况一定是碰上不直接传火,而是跟着它走然后火没了再续上,这样就变成了只有一个移动的火把,然后遇到人可以续火。然后我们也一定是一直保持走动,遇到人再回头。我们我们遇到人之后,只需要考虑向左走还是向右走,于是相当于就是把 \\(k\\) 之前的人和 \\(k\\) 之后的人分开,\\(a\\) 为和前面一个人的距离,然后每次从两个队列中选一个头然后删掉,并获得 \\(a-t\\) 的代价。考虑划分连续段,先从前往后,如果段和 \\(>0\\) 就截断,可以截成一些和 \\(>0\\) 的段,然后记录每个段的最小前缀和,就可以贪心了。最后会剩下一个段和 \\(<0\\) 的段,可以从后往前考虑,就是相同的问题了,并且一定是能划分完的。复杂度 \\(O(n\\log V)\\)。
https://qoj.ac/submission/108573
JOISC2017 Day2 星穹铁道 Railway Trip
首先肯定是每个点不断往比自己大的点跳,然后两边同时跳到同一个点。考虑我们按照这种题的套路,每次记录能跳到的最左侧的点和最右侧的点,这样跳 \\(x\\) 步跳到的端点是唯一的。注意到,如果 \\(u,v\\) 分别跳 \\(x,y\\) 步到的这样的端点恰好无交,那么 \\(u\\) 的端点再跳一步一定能到达 \\(v\\) 的一个端点。而跳到恰好无交的过程是唯一的,所以直接先倍增 \\(x\\) 再倍增 \\(y\\) 即可。
https://qoj.ac/submission/108495
JOISC2017 Day2 门票安排 Arranging Tickets
见 https://www.cnblogs.com/TetrisCandy/p/17166793.html。这题过于厉害了。
JOISC2017 Day3 幽深府邸 Long Mansion
法一:直接暴力扩展,要注意继承能经过的点的答案,复杂度是正确的,但是不大会证明。
法二:令 \\(f_l\\) 表示 \\(>l\\) 的位置的第一次出现 \\(c_l\\) 的位置,\\(g_r\\) 表示 \\(<r\\) 的位置的第一次出现 \\(c_r\\) 的位置,那么对于 \\(i\\) 要找到极小的 \\(i\\in [l,r]\\) 满足 \\(r<f_l\\) 且 \\(l>g_r\\)。对于每个 \\(l\\) 记录最小的满足要求的 \\(r\\),然后更新区间的答案。
https://qoj.ac/submission/108097
JOISC2017 Day3 自然公园 Natural Park
首先考虑一种链的做法:维护一条包含 \\(0\\) 的链 \\(x\\to y\\),对于要新加入的节点 \\(z\\),问出在 \\(x\\) 左边还是 \\(y\\) 右边。设在 \\(y\\) 右边,考虑 \\(W(y,z)\\) 表示问出 \\(y,z\\) 中间的链的过程,我们通过二分问出 \\(y\\to z\\) 中的最小的点 \\(w\\),然后 \\(W(y,w)\\),\\(W(w,z)\\) 即可求出答案。
然后考虑树的做法:考虑维护当前的包含 \\(0\\) 的连通块,然后每次新加入的 \\(z\\),先问出一个 \\(y\\) 使得 \\(y\\to z\\) 的路径使得不经过原连通块。这个可以边分治,然后看哪一半树上存在这样的 \\(y\\)。这同样可以对 dfs 序进行二分得到。问出 \\(y\\) 之后就可以 \\(W(y,z)\\) 了,分治理论复杂度 \\(O(n\\log_1.2n)\\),但是跑不满。
然后考虑图的情况:我们仍然找到类似的 \\(y\\),方法仍然是对生成树进行边分治/二分,但是每次将一个点和原连通块相连的时候,需要把所有邻点问出来,这个和求 \\(y\\) 的方法本质相同,理论次数 \\(kn\\log_1.2n\\),但是稍微剪枝一下就可以直接过。
https://qoj.ac/submission/108269
JOISC2017 Day4 绑架2 Abduction 2
考虑直接记忆化搜索,每次找到拐弯的点。复杂度我也不大会正,但是看上去非常对。
「JOISC 2017 Day 3」自然公园 题解
因为感觉网上题解很少,所以想来写一点感性+理性混合的题解qwq
而且犯了很多弱智错误)
考虑我们可以以以下形式确定每一条边:
我们维护一个包含0号节点的连通块 每次挑选一个点 x x x,满足 x x x 有和连通块直接相连的边
我们的任务就是:如何确定这样一条边?
直接扫肯定是不可接受的,所以我们采用二分的形式:
确定一个序(dfs/bfs)均可,然后在这个序上进行二分,二分出哪个前缀是满足联通的最小前缀(就是只包含这个前缀里的节点时,可以满足仍然有直接连边)
这样,前缀里的最后一个点,就是和这个点有直接连边的点
然后删去这个直接相连的点,会分出若干连通块,再检验 x x x 和这些连通块是否有直接连边
考虑为什么检验次数是对的,首先显然会有 M l o g n Mlogn Mlogn 次检验(不过这个log应该比较小)
然后考虑,删了一个点之后会分出若干连通块,就算立马返回,也要一次check
那么对于菊花来说,复杂度是否正确呢?
因为题目限制最多7条边,所以最多分出六个连通块,也就是总复杂度应该是 7 M l o g 7Mlog 7Mlog
鉴于很难每个点都跑到上届,所以可过)
那么另一个问题 我们如何找到一个 x x x 呢?
事实上 我们每次可以随机联块外一个点 x x x,随机的必要性在于防止被精心构造的数据多卡出一个 N l o g Nlog Nlog的check
然后对于这个 x x x 我们可以找到它和连通块之间的一点 y y y
但注意 我们应该标记连通块外的所有点,当以它去找路径中点的时候,应该把它删掉,理由有2:
1.他已经不可能成为其他的点的,路径上的必须点了(再搜完它前面的点之后 它会直接成为连通块,而它不可能成为它前面的点的必须点)
2.避免连通块之间胡搜,可能会出现(测试出的)连通块之间互搜的情况
#include "park.h"
#include <bits/stdc++.h>
using namespace std;
int const N = 1410;
struct stu1
int y, nex;
e[N * 5];
int in[N], out[N], t[N], lin[N], tot, cnt, tim, vis[N], q[N], num, pl[N], n;
bool check(int x)
in[x] = 1;
int op = Ask(0, x, in);
in[x] = 0;
return op;
void bfs(int x)
int l = 1, r = 1;
q[1] = x;
++tim;
int dq, y;
tot = 0;
vis[x] = tim;
while (l <= r)
dq = q[l++];
t[++tot] = dq;
for (int i = lin[dq]; i; i = e[i].nex)
y = e[i].y;
if (in[y] && vis[y] < tim)
q[++r] = y;
vis[y] = tim;
bool ask(int x, int y, int lim)
memset(pl, 0, sizeof(pl));
for (int i = 1; i <= lim; i++)
pl[t[i]] = 1;
pl[y] = 1;
return Ask(min(x, y), max(x, y), pl);
void link(int x, int y)
e[++cnt] = (stu1)
y, lin[x]
;
lin[x] = cnt;
void extend(int ro, int x)
bfs(ro);//删了一个点之后 会变成若干连通块 (等等 对于菊花来说 复杂度为什么是对的... 因为只有七条边所以复杂度是对的)
if (!ask(ro, x, tot))
return;
int mid, l = 1, r = tot; //找到序最小的一个直接相连的位置
while (l < r)
mid = (l + r) >> 1;
if (ask(ro, x, mid))
r = mid;
else
l = mid + 1;
int z = t[l], dfn = tim;
in[z] = 0;
for (int i = lin[z]; i; i = e[i].nex)
int y1 = e[i].y; //注意 我这里写的和参考程序有出入 参考程序开多个p的目的就在于这里
if (in[y1] && vis[y1] <= dfn)
extend(y1, x);
in[z] = 1;
link(z, x);
link(x, z);
Answer(min(x, z), max(x, z));
void work(int
x) //把它调出out集合的原因是: 虽然不会出现xy直接互搜的情况 但是可能会出现循环节互搜的情况..
//而且调出去一定不错 目的是找到和连通块直接连接的点 可以确定这个点不和连通块直接连接
out[x] = 0;
int mid, l, r, d = 0;
tot = 0;
while (!check(x))
d = 0; //while可能会跑多次 要清d
for (int i = 0; i < n; i++)
if (in[i])
t[++d] = i;
l = d + 1;
for (int i = 0; i < n; i++)
if (out[i])
t[++d] = i;
r = d;
while (l < r)
mid = (l + r) >> 1;
if (ask(0, x, mid))
r = mid;
else
l = mid + 1;
work(t[l]);
extend(0, x);
num++;
in[x] = 1; //找出所有x向连通块里连的边
void Detect(int T, int
N) //我不是很懂为什么要rand一个点出来 所以我不rand 盲猜一下 应该是怕被精心构造的数据卡之类的?
in[0] = 1;
num = 1;
n = N; //还是需要rand的 不然最劣可以跑到8Mlog左右(大概)
for (int i = 1; i < N; i++)
out[i] = 1;
int x;
while (num < N)
x = rand() % N;
while (in[x])
x = rand() % N; //最坏n^2次 总之应该是能随完的)
work(x); //处理i号点和连通块里所有点的连边 然后把它加连通块里
以上是关于JOISC 2017 题解的主要内容,如果未能解决你的问题,请参考以下文章