「JOISC 2017 Day 3」自然公园 题解
Posted Flame♡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「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 Day 3」自然公园 题解的主要内容,如果未能解决你的问题,请参考以下文章
LOJ 3276 JOISC 2020 Day2 遗迹 题解 (计数DP)