2026 WUST选拔赛Round1 L题题解#
原题链接:Nowcoder ↗
题意:#
在 到 上有 个位置,每个位置放置了一个弹簧,弹簧能将弹球弹到固定的点 上。
接下来要进行 个操作:在第 个操作中,你需要放置一个拥有无限动力的弹球在 处,并回答在 处的弹球个数 。
注意:操作的顺序为:先放入一个弹球,询问当前位置的弹球数量,再所有弹球沿边移动一步。
已知 。给定位置个数 ,操作数 和数组 , 。保证所有弹球都会被放置在 中。
思路:#
-
转化题意
由于每个位置的弹球都会在一次操作中从位置 被移到 ,因此可以将每一个位置看成一个点,并连接一条有向边,形成了由若干个基环树组成的函数图。因此,此题可以转化为: 在由若干个基环树组成的函数图上,支持动态加入弹球,并在线查询某个时刻某个点上的弹球数量。
-
非环点
对于不在环上的点,弹球会不断沿边移动,直到第一次到达唯一的,且在其所在基环树的环上的点为止。对于任意一个基环树来说,每一个非环点都在以某个唯一的,且在其所在基环树的环上的点为根的树上。因此,我们可以建立反图,利用树的结构去维护位置。
接下来需要维护非环点上弹球的位置。设在第 次操作中在非环点 放入弹球,在第 次操作中询问非环点 ,且这个弹球在点 上。利用树的结构,我们可以知道弹球从 移动到点 所需要的时间为点 在树上的深度减去 的在树上的深度,设 表示点 的深度,即有:,变形可以得到:
另外,点 一定位于以点 为根的子树中。因此,不仅要满足 的时间条件,也要满足路径条件。可以对每一个树都维护一个 序,设点 和 分别为点 的 进入时间和点 子树内最大的 进入时间,则需满足:
因此,查询非环点 时,只需统计满足且 在 之间的历史插入点 。因此,可以按照 的值进行分类,对于每一个 来说,使用对应的平衡树维护其插入点的序,并统计 之间的数目即可。我们可以使用
unordered_map<int, ordered_set<array <int, 2> >进行维护。其中 ,ordered_set中存储{tin[u], t},防止同一个点多次插入后仍计算为同一个点。 -
环点
对于环上的点,弹球会不断沿环上的边移动。设在第 次操作中在环点 放入弹球,环点 所在的环的长度为 ,环点编号为 。则在第 次操作中,弹球会移动到编号为 上,变形有:
因此,与非环点类似, 仍然是一个不变量 ,利用简单数组统计有相同 的点的数量即可。
除此之外,环点还需要维护弹球从非环点走到环上的弹球数量。对于每一个在非环点的弹球,可以利用其树的深度来快速算出其进入环点的时间,可以在对应的时间把这些点当作环点加入环内即可。
代码:#
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
#define int long long
template <typename T>
using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
using arr2 = array <int, 2>;
void solve ()
{
int n;
cin >> n;
vector <int> a(n + 1), ind(n + 1);
vector <vector <int> > rev(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
ind[a[i]]++;
rev[a[i]].push_back(i);
}
// 一.建基环树
// 1.topo判环
vector <int> removed(n + 1);
queue <int> q;
for (int i = 1; i <= n; i++) {
if (ind[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
auto u = q.front();
q.pop();
removed[u] = true;
int v = a[u];
if (--ind[v] == 0) q.push(v);
}
// 2.建环
vector <vector <int> > cycle;
vector <int> vis(n + 1, 0);
int id = 0;
for (int i = 1; i <= n; i++) {
if (removed[i] || vis[i]) continue;
vector <int> cyc;
int u = i;
while (!vis[u]) {
vis[u] = 1;
cyc.push_back(u);
u = a[u];
}
cycle.push_back(cyc);
}
// 3.初始化树的信息
vector <int> comp(n + 1), pos(n + 1), dep(n + 1), root(n + 1);
int sum_cyc = cycle.size();
for (int id = 0; id < sum_cyc; id++) {
for (int i = 0; i < cycle[id].size(); i++) {
int u = cycle[id][i];
comp[u] = id;
pos[u] = i;
dep[u] = 0;
root[u] = u;
}
}
// 4.建反树
vector <vector <int> > e(n + 1);
for (int u = 1; u <= n; u++) {
for (auto v : rev[u]) {
if (removed[v]) {
e[u].push_back(v);
}
}
}
// 5.求dfs序
vector <int> tin(n + 1), tout(n + 1);
int time = 0;
auto dfs = [&] (auto self, int u) -> void {
tin[u] = ++time;
for (auto v : e[u]) {
comp[v] = comp[u];
root[v] = root[u];
dep[v] = dep[u] + 1;
self(self, v);
}
tout[u] = time;
};
for (auto &cyc : cycle) {
for (auto r : cyc) {
dfs(dfs, r);
}
}
// 二.计算答案
// 1.初始化
int m;
cin >> m;
vector <int> len(sum_cyc);
vector <vector <int> > cyc_info(sum_cyc);
for (int id = 0; id < sum_cyc; id++) {
len[id] = cycle[id].size();
cyc_info[id].assign(len[id], 0);
}
vector <vector <arr2> > update(m + 1);
unordered_map <int, ordered_set<arr2> > mp;
mp.reserve(m * 2 + 10);
int pre = 0;
for (int i = 1; i <= m; i++) {
for (auto [id, c_id] : update[i]) {
cyc_info[id][c_id]++;
}
int x;
cin >> x;
x = (x ^ pre);
// 2.插入
if (!removed[x]) {
int id = comp[x];
int c_id = ((pos[x] - i) % len[id] + len[id]) % len[id];
cyc_info[id][c_id]++;
}else {
int key = i + dep[x];
mp[key].insert({tin[x], i});
int T = i + dep[x];
if (T <= m) {
int id = comp[x];
int c_id = ((pos[root[x]] - T) % len[id] + len[id]) % len[id];
update[T].push_back({id, c_id});
}
}
// 3.查询
int res = 0;
if (!removed[x]) {
int id = comp[x];
int c_id = ((pos[x] - i) % len[id] + len[id]) % len[id];
res = cyc_info[id][c_id];
}else {
int key = i + dep[x];
auto it = mp.find(key);
if (it != mp.end()) {
auto &st = it->second;
res = st.order_of_key({tout[x] + 1, -1}) - st.order_of_key({tin[x], -1});
}
}
cout << res << '\n';
pre = res;
}
}
int32_t main ()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _ = 1;
// cin >> _;
while (_--) {
solve();
}
return 0;
} c