首页 > 其他分享 >[CCO2021] Through Another Maze Darkly 题解

[CCO2021] Through Another Maze Darkly 题解

时间:2025-01-13 23:12:21浏览次数:1  
标签:遍历 int 题解 Darkly Through 行走 朝上 扩充 指针

思路很牛,代码很难,故写题解记之。

前置知识:线段树。

洛谷题目链接:P7830 [CCO2021] Through Another Maze Darkly

原题题面很简洁,故不赘述题意。


观察(70%)

读完题,这是什么东西。

我们直接去手玩样例,然后发现几个很有用的信息:

  1. 一个点被进入一次后,必然会指针朝上。

  2. 一个点被进入两次后,其所有儿子都被进入过了。具体的,如果一开始指针朝上,那么第一次进入即可遍历所有儿子;否则第一次会遍历第一个进入的儿子以及之后的儿子(然后就指针朝上了),第二次就能遍历所有儿子。

  3. 以 \(1\) 号节点为根的、指针朝上的连通块,每次从根的一次行走路径相同,且连通块可能往外扩充。

  4. (即 \((3)\) 的最终情况)当一个子树里指针全部朝上,那么以后每次进入该子树,都会以相同的路径行走。这就导致最后整棵树指针全部朝上,并且行走进入周期。

于是这题你就已经完成 \(70\%\) 了。所以请务必理解以上信息。

查询(85%)

剩下中,有个 \(15\%\) 是想到怎么处理询问的东西。

注意到我们行走的过程可以拆成若干轮从根(\(1\))开始的行走。

而每轮行走实际上是对一个包含根的连通块的遍历,是一个欧拉序列

此外,对观察到的性质 \((3)\) 进一步推理,不难发现每轮行走的欧拉序列都会是下一轮行走的欧拉序列的子序列。

那是不是相当于,本来有一个空串,然后每走一轮就会扩充一些点,会插入一小串欧拉序列。

然后我们再对询问排个序,每轮行走完毕后处理一下最后停在这轮的询问。那现在无非就是查询这串手里的欧拉序列的第 \(x\) 项停留在哪个点上了。

显然可以平衡树,但是我不喜欢。

【此处缺少一张图】

实现的细节(100%)

还有 \(15\%\) 是想好怎么模拟连通块扩充。这个我没有想好。

下面就是个对实现方式的介绍。如果你代码功底好的话,还是尝试自己写写。

首先想想扩充的本质。我们以前都学过用 bfs 做洪水填充这类题,这个就很相似。不太一样的是,这题我们要模拟每一轮遍历,而且一次遍历可能多处扩充。因此不难想到,维护一个队列,里面放这一轮要去扩充的节点;每轮里把队列给弹完,然后在本次的扩充中别急着进队,要本轮结束再一起进(总之就是要避免这一轮把下一轮的东西处理了)。
那每一个弹出的节点,就直接用 dfs 扩充就好了。其中分为两步:

  1. 把那个指针指到的儿子递归下去,直到指针指回父亲。

  2. 把没有进入的儿子入队(但是要间接入队)。

说的很虚幻,看看代码就一下清楚了。

这就是那 \(15\%\) 的部分了,主要是“扩充”的实现。

代码

注释会尽量详细。尽管我相信应该没什么人能看得懂。

【加注释】

// Author: Aquizahv
#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<ll, int> pli;
const int N = 8e5 + 5, ND = N << 3;
int n, m, p[N], fa[N];
vector<int> g[N];
int pos, head[N];
struct Edge
{
    int u, v, id, nxt;
} e[N];
int eucnt, eu[N << 1], in[N], out[N];
int cnt, ans[N];

void dfs1(int u, int Fa)
{
    fa[u] = Fa;
    for (auto v : g[u])
        if (v != Fa)
            dfs1(v, u);
}

void addEdge(int u, int v)
{
    // cout << "    " << u << ' ' << v << endl;
    e[++pos] = {u, v, 0, head[u]};
    head[u] = pos;
}

void dfs2(int u)
{
    // dfn[u] = ++dfncnt;
    eu[++eucnt] = u;
    in[u] = eucnt;
    for (int i = head[u]; i; i = e[i].nxt)
    {
        dfs2(e[i].v);
        e[i].id = eucnt;
        eu[++eucnt] = u;
    }
    out[u] = eucnt;
}

#define lson u + u
#define rson u + u + 1
struct segtree
{
    int sum[ND];
    void pushup(int u)
    {
        sum[u] = sum[lson] + sum[rson];
    }
    void update(int u, int l, int r, int it)
    {
        if (it < l)
            return;
        if (l == r)
        {
            // cout << "---------" << l << endl;
            sum[u]++;
            cnt++;
            return;
        }
        int mid = (l + r) >> 1;
        if (it <= mid)
            update(lson, l, mid, it);
        else
            update(rson, mid + 1, r, it);
        pushup(u);
    }
    int query(int u, int l, int r, int k)
    {
        if (l == r)
            return l;
        int mid = (l + r) >> 1;
        if (k <= sum[lson])
            return query(lson, l, mid, k);
        else
            return query(rson, mid + 1, r, k - sum[lson]);
    }
} t;

vector<int> tv;
void dfs3(int u)
{
    t.update(1, 2, eucnt, in[u]);
    // cout << "+ " << in[u] << ' ' << u << ' ' << e[p[u]].v << endl;
    for (int i = p[u]; i; i = e[i].nxt)
    {
        dfs3(e[i].v);
        t.update(1, 2, eucnt, e[i].id + 1);
        // cout << "+ " << e[i].id + 1 << endl;
    }
    for (int i = head[u]; i != p[u]; i = e[i].nxt)
    {
        // cout << u << ' ' << e[i].v << endl;
        tv.push_back(i);
    }
    p[u] = head[u];
}

int main()
{
#ifdef Aquizahv
    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout);
#endif
    cin >> n >> m;
    for (int u = 1; u <= n; u++)
    {
        int k, v;
        scanf("%d", &k);
        for (int j = 1; j <= k; j++)
            scanf("%d", &v), g[u].push_back(v);
    }
    dfs1(1, 0);
    addEdge(1, g[1][0]);
    for (int i = g[1].size() - 1; i >= 1; i--)
        addEdge(1, g[1][i]);
    p[1] = pos;
    for (int u = 2; u <= n; u++)
    {
        bool flag = false;
        reverse(g[u].begin(), g[u].end());
        // if (u == 4)
        //     cout << "sp: " << g[u].size() << ' ' << g[u][0] << ' ' << g[u][1] << endl;
        for (int i = 0; g[u][i] != fa[u] || !flag; i = (i + 1) % g[u].size())
        {
            if (g[u][i] == fa[u])
                flag = true;
            else if (flag)
                addEdge(u, g[u][i]);
            if (flag && i == g[u].size() - 2)
            {
                // cout << u << ' ' << i << ' ' << g[u][i] << endl;
                p[u] = e[pos].u == u ? pos : 0;
            }
        }
    }
    dfs2(1);
    // for (int i = 1; i <= eucnt; i++)
    //     printf("%d%c", eu[i], " \n"[i == eucnt]);
    vector<pli> qu;
    ll x;
    for (int i = 1; i <= m; i++)
        scanf("%lld", &x), qu.push_back({x, i});
    sort(qu.begin(), qu.end(), greater<pli>());
    cnt = 0;
    ll tot = 0;
    // int I = 0;
    queue<int> q;
    q.push(1);
    while (!q.empty())
    {
        // cout << ++I << endl;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            dfs3(u);
        }
        while (!qu.empty() && qu.back().first <= tot + cnt)
        {
            ans[qu.back().second] = t.query(1, 2, eucnt, qu.back().first - tot);
            // cout << qu.back().second << ' ' << qu.back().first << ' ' << qu.back().first - tot << ' ' << ans[qu.back().second] << ' ' << cnt << endl;
            qu.pop_back();
        }
        tot += cnt;
        while (!tv.empty())
        {
            q.push(e[tv.back()].v);
            t.update(1, 2, eucnt, e[tv.back()].id + 1);
            tv.pop_back();
        }
    }
    while (!qu.empty())
    {
        ans[qu.back().second] = t.query(1, 2, eucnt, (qu.back().first - tot - 1) % cnt + 1);
        qu.pop_back();
    }
    for (int i = 1; i <= m; i++)
        printf("%d\n", eu[ans[i]]);
    return 0;
}

如果有任何错误或者疑问,欢迎评论或私信指出!

If you can,点个赞呗。

标签:遍历,int,题解,Darkly,Through,行走,朝上,扩充,指针
From: https://www.cnblogs.com/aquizahv/p/18669593

相关文章

  • 英雄联盟游戏黑屏有声音 原因分析及问题解决
    英雄联盟作为一款热门游戏,经常有玩家遇上电脑黑屏,但却可以听到游戏内的声音,这是怎么回事?这通常意味着显卡驱动程序可能遇到了某些问题,也可能与游戏文件损坏、系统设置不当等因素有关。以下是一些常见的解决方法:一、显卡问题更新显卡驱动:显卡驱动不兼容或过时可能导致游戏......
  • 题解:AT_abc353_f [ABC353F] Tile Distance
    [ABC353F]TileDistance题解cnblogs题目传送门:洛谷,AtcoderSolution很恶心人的分类讨论题。很显然走大格子大概率比走小格子快。对终点和起点向上下左右枚举大格子,我们就把问题转化为给两个大格子\((a,b)\)、\((c,d)\),求怎样走最快。对角的大格子可以通过\(2\)步相互到......
  • ABC388DEG 题解
    ABC388题解ABCDE+G,rk371。D观察到几个性质:一个人只会在成年的时候得到石头,在成年之后给出石头。第\(i\)个人成年之后,他要给之后的每个人一个石头(除非用光了)。也就是说,假设成年时它的石头数量为\(B_i\),则最终他的石头数量为\(\max(0,B_i-(n-i))\)。因此我们只需......
  • LeetCode 2275: 按位与结果大于零的最长组合题解
    LeetCode2275:按位与结果大于零的最长组合题解1.题目分析这道题目考察了位运算的基本概念和应用。我们需要在给定的数组中找出最长的子序列,使得这些数字进行按位与运算后的结果大于0。1.1关键概念按位与运算(&)两个二进制位都为1时,结果为1。只要有一个为0,结果就为0......
  • 【大数据】beeline 导出文件有特殊字符的问题解决
    问题近期在做大数据查询引擎导出文件的功能,使用了hive的beeline客户端。发现beeline导出的文件以及终端输出有许多特殊字符。按照官方文档使用beeline导出命令:[1]/usr/bin/beeline--silent=true--showHeader=true--outputformat=csv2-fquery.hql</dev/null>/tm......
  • Hetao P3804 Cut 题解 [ 蓝 ] [ AC 自动机 ] [ 差分 ]
    Cut:AC自动机简单题。思路看见多个模式串以及求前缀,很显然能想到构建一个AC自动机。那么在用\(T\)查询时,当前指针的深度就是该位置的最长前缀匹配长度。这个在字典树insert的时候就能求出来。求出每一位的最长前缀后,因为这些部分都不能作为分割点,所以将这些区域用差分......
  • 验证出栈顺序是否正确题解&队列
    (1)验证出栈顺序是否正确队列遵循先入后出的原则,故需要一个数组来模拟记录入栈和出栈再分别对出栈顺序进行遍历验证是否正确#include<iostream>usingnamespacestd;#definem100000intb[m],a[m],c[m];intmain(){ intt; cin>>t; while(t--){ intn;......
  • THUWC 2020 Day3 题解
    Cache一致性协议按照学习手册最后的模拟。\(\text{Exclusive}/\text{Shared}\)只有编号最小的返回,但都要改变状态。\(\text{Modified}\)的所有的都要返回且改变状态。Cache替换算法这里说一下\(\text{PLRU}\)算法。对于每次,先找是否命中。如果是否,就在二叉搜索树上......
  • 2021 年 3 月青少年软编等考 C 语言五级真题解析
    目录T1.红与黑思路分析T2.密室逃脱思路分析T3.求逆序对数思路分析T4.最小新整数思路分析T1.红与黑有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计......
  • Codeforces Round 734 (Div. 3) 题解
    建议开题顺序:A,B1,B2,C,E,F,D1,D2。A.PolycarpandCoins记\(k=\min(c1,c2)\),则\((c1-k)\times1+(c2-k)\times2+k\times3=n\)。注意到\(n\mod3\)为\(0,1,2\)。所以我们\(|c1-c2|\)最多为\(1\),只需要将\(n\mod3\)给\(1\)或\(2\)即可。B1.WonderfulColo......