思路很牛,代码很难,故写题解记之。
前置知识:线段树。
洛谷题目链接:P7830 [CCO2021] Through Another Maze Darkly。
原题题面很简洁,故不赘述题意。
观察(70%)
读完题,这是什么东西。
我们直接去手玩样例,然后发现几个很有用的信息:
-
一个点被进入一次后,必然会指针朝上。
-
一个点被进入两次后,其所有儿子都被进入过了。具体的,如果一开始指针朝上,那么第一次进入即可遍历所有儿子;否则第一次会遍历第一个进入的儿子以及之后的儿子(然后就指针朝上了),第二次就能遍历所有儿子。
-
以 \(1\) 号节点为根的、指针朝上的连通块,每次从根的一次行走路径相同,且连通块可能往外扩充。
-
(即 \((3)\) 的最终情况)当一个子树里指针全部朝上,那么以后每次进入该子树,都会以相同的路径行走。这就导致最后整棵树指针全部朝上,并且行走进入周期。
于是这题你就已经完成 \(70\%\) 了。所以请务必理解以上信息。
查询(85%)
剩下中,有个 \(15\%\) 是想到怎么处理询问的东西。
注意到我们行走的过程可以拆成若干轮从根(\(1\))开始的行走。
而每轮行走实际上是对一个包含根的连通块的遍历,是一个欧拉序列。
此外,对观察到的性质 \((3)\) 进一步推理,不难发现每轮行走的欧拉序列都会是下一轮行走的欧拉序列的子序列。
那是不是相当于,本来有一个空串,然后每走一轮就会扩充一些点,会插入一小串欧拉序列。
然后我们再对询问排个序,每轮行走完毕后处理一下最后停在这轮的询问。那现在无非就是查询这串手里的欧拉序列的第 \(x\) 项停留在哪个点上了。
显然可以平衡树,但是我不喜欢。
【此处缺少一张图】
实现的细节(100%)
还有 \(15\%\) 是想好怎么模拟连通块扩充。这个我没有想好。
下面就是个对实现方式的介绍。如果你代码功底好的话,还是尝试自己写写。
首先想想扩充的本质。我们以前都学过用 bfs 做洪水填充这类题,这个就很相似。不太一样的是,这题我们要模拟每一轮遍历,而且一次遍历可能多处扩充。因此不难想到,维护一个队列,里面放这一轮要去扩充的节点;每轮里把队列给弹完,然后在本次的扩充中别急着进队,要本轮结束再一起进(总之就是要避免这一轮把下一轮的东西处理了)。
那每一个弹出的节点,就直接用 dfs 扩充就好了。其中分为两步:
-
把那个指针指到的儿子递归下去,直到指针指回父亲。
-
把没有进入的儿子入队(但是要间接入队)。
说的很虚幻,看看代码就一下清楚了。
这就是那 \(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,点个赞呗。