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

考虑直接记忆化搜索,每次找到拐弯的点。复杂度我也不大会正,但是看上去非常对。

https://qoj.ac/submission/108328

「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 题解的主要内容,如果未能解决你的问题,请参考以下文章

题解[JOISC2020] 治療計画

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

LOJ 3276 JOISC 2020 Day2 遗迹 题解 (计数DP)

JOISC2017C 手持ち花火

JOISC2019|201920190622cake3

JOISC2020 自闭记