CF1930H 题解

· · 题解

\textbf{CF1930H *3400}

题目链接。

  • 给你一棵 n 个点的树,每个点的点权 \{a_i\}0 \sim n - 1 的排列,q 次询问,每次查询 u \to v 的路径 \operatorname{MEX}

  • 排列是交互库隐藏的且每次询问的排列 a 不同。你可以在所有 q 次询问开始前构造两个 1\sim n 排列 p_1,p_2,每次向交互库给出 t,l,r,得到 \min \limits_{i=l}^r \{a_{p_{t,i}}\} 的值。只有 5 次询问机会。

首先对于排列 a,有结论:子序列 a'\operatorname{MEX} 等于其补集 b\min,证明显然。那么我们就有做法:对原树进行重链剖分,则 u \to v 的路径会被拆成 \mathcal O(\log n) 段连续 dfn 区间,则这些区间的补集也是 \mathcal O(\log n) 段 dfn 区间,令 p_1dfn 数组,则暴力问出这些区间 \min 然后取 \min。这也就是题面中提到的 HLD 解法,遗憾的是我们只有 5 次询问机会。

我们发现上述解法完全没有用到排列 p_2,没有充分利用条件。解决这道题最关键的地方是想到欧拉序。令 p_1 为 dfs 序的排列(也即欧拉序的 in),p_2 为欧拉序的 out 的排列(离开 u 子树时将 u 计入数组)。

考虑怎么利用这两个排列计算 \min。以下令 dfn_u < dfn_v

核心性质是:对于 u,其祖先的 dfn 小于其 dfn,而 out 大于其 out,我们可以用这些性质去除链上的节点。

此时 out_v \le out_u,对于任意 i 满足 dfn_i < dfn_udfn_i > dfn_vi 一定不在 u \to v 的链上;同理,对于 i 满足 out_i < out_vi 也一定不在 u \to v 的链上。

我们可以发现所有不在链上的点全都已经被去除。这个是显然的:对于非 v 祖先 i,如果 iv 以前遍历,则 out_i < out_v;如果 iv 以后遍历,则 dfn_i > dfn_v。当然记得判掉 i1 \sim u 的链上。

此时不在链上的点有五种类型,对应的 dfnout 见下图(dfs 顺序从左到右)。

(PS:下图左侧部分应为 out 而不是 dfn,但是图做好懒得修了 /kk)

u=10,v=13 只需要把上面五部分在两个排列上问出来 $\min$,取 $\min$ 输出即可。倍增实现,时间复杂度 $\mathcal O((n+q) \log n)$。 注意特判 $\operatorname{MEX} = n$ 的情况。 ```cpp /** * author: sunkuangzheng * created: 18.02.2024 15:45:13 **/ #include<bits/stdc++.h> #ifdef DEBUG_LOCAL #include <mydebug/debug.h> #endif using ll = long long; const int N = 5e5+5; using namespace std; int T,n,q,p1[N],p2[N],q1[N],d,q2[N],tot1,fk,mn[N],k,tot2,fa[N][20],u,v,siz[N],dep[N]; vector<int> g[N]; void dfs(int u,int f){ p1[++tot1] = u,q1[u] = tot1,fa[u][0] = f,siz[u] = 1,dep[u] = dep[f] + 1,mn[u] = 1e9; for(int i = 1;i <= __lg(dep[u]);i ++) fa[u][i] = fa[fa[u][i-1]][i-1]; for(int v : g[u]) if(v != f) dfs(v,u),siz[u] += siz[v],mn[u] = min(mn[u],mn[v]); p2[++tot2] = u,q2[u] = tot2,mn[u] = min(mn[u],tot2); }int lca(int u,int v){ if(dep[u] < dep[v]) swap(u,v); while(dep[u] > dep[v]) u = fa[u][__lg(dep[u] - dep[v])]; for(int i = __lg(dep[u]);i >= 0;i --) if(fa[u][i] != fa[v][i]) u = fa[u][i],v = fa[v][i]; return (u == v ? u : fa[u][0]); }int kfa(int u,int k){ if(k < 0) return 0; for(int i = __lg(dep[u]);i >= 0;i --) if((k >> i) & 1) u = fa[u][i]; return u; }void los(){ cin >> n >> q,tot1 = tot2 = 0; for(int i = 1;i <= n;i ++) g[i].clear(); for(int i = 1;i <= n;i ++) for(int j = 0;j <= 19;j ++) fa[i][j] = 0; for(int i = 1;i < n;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u); dfs(1,0); for(int i = 1;i <= n;i ++) cout << p1[i] << " "; cout << endl; for(int i = 1;i <= n;i ++) cout << p2[i] << " "; cout << endl; auto ask = [&](int t,int l,int r){ if(l > r || l <= 0 || r <= 0) return (int)1e9; return cout << "? " << t << " " << l << " " << r << endl,cin >> k,k; }; while(q --){ cin >> u >> v; int ans = n,d = lca(u,v); if(q1[u] > q1[v]) swap(u,v); if(d == u){ ans = min(ans,ask(2,1,q2[v] - 1)),ans = min(ans,ask(1,q1[v] + 1,n)); ans = min(ans,ask(1,1,q1[d] - 1)),cout << "! " << ans << endl,cin >> fk,assert(fk == 1); continue; }ans = min(ans,ask(2,1,q2[u] - 1)),ans = min(ans,ask(1,q1[v] + 1,n)); int u1 = kfa(u,dep[u] - dep[d] - 1),v1 = kfa(v,dep[v] - dep[d] - 1); ans = min(ans,ask(1,q1[u] + 1,q1[v1] - 1)), ans = min(ans,ask(2,mn[v1],q2[v] - 1)),ans = min(ans,ask(1,1,q1[d] - 1)); cout << "! " << ans << endl,cin >> fk; assert(fk == 1); } }int main(){ ios::sync_with_stdio(0),cin.tie(0); for(cin >> T;T --;) los(); } ```