「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)

题解[JOISC2020] 治療計画

题解[JOISC2020] カメレオンの恋

! JOISC2020DAY2变色龙之恋

NOIP2017 Day1 T3 逛公园(最短路+拓扑排序+DP)

JOISC2019|201920190622cake3