CF1930H 题解
sunkuangzheng
·
·
题解
\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_1 为 dfn 数组,则暴力问出这些区间 \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_u 或 dfn_i > dfn_v,i 一定不在 u \to v 的链上;同理,对于 i 满足 out_i < out_v,i 也一定不在 u \to v 的链上。
我们可以发现所有不在链上的点全都已经被去除。这个是显然的:对于非 v 祖先 i,如果 i 在 v 以前遍历,则 out_i < out_v;如果 i 在 v 以后遍历,则 dfn_i > dfn_v。当然记得判掉 i 在 1 \sim u 的链上。
此时不在链上的点有五种类型,对应的 dfn 或 out 见下图(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();
}
```