题解:CF2164G Pointless Machine
先来看一看通过这个操作能得到什么。
首先,容易看出可以对询问结果差分,记差分数组为 \(a_i\),则 \(a_i\) 表示 \(p_i\) 与 \(p_{1\dots i-1}\) 的连边数量。
那么,只要把 \(p\) 做一次反转操作再询问,就可以获得 \(p_i\) 与 \(p_{i+1\dots n}\) 的连边数量,相加就可以得到每个点的度数 \(dg_i\)。
然而,只知道度数是不足以确定一棵树的,思考完成此题还缺失了什么信息。
想要确定一颗树,从度数入手的话可以考虑拓扑结构。此时,度数为 \(1\) 的点就是叶子,只要得知了这个叶子连在哪个节点上,就可以把这个叶子去掉,然后不停地删叶子,直到确定整棵树。
那么,如何得到度数为 \(1\) 的点的邻点呢?
先考虑维护每个点的邻点集合,但这很难得到,同时我们只需要的到度数为 \(1\) 的点的邻点,不用维护其他点的邻点集合。
此时,考虑求邻点的编号和,这样就可以满足在度数为 \(1\) 的时候直接得到邻点,而又不需要维护更多信息。
维护编号和其实是可做的,考虑拆位,分别求出每一个二进制位在邻点编号出现的次数,再求和。
假设我们当前考虑到了第 \(k\) 位,把第 \(k\) 位为 \(0\) 的数排成一个序列 \(A\),第 \(k\) 位为 \(1\) 的数排成一个序列 \(B\)。
按照 \(AB\) 的顺序拼接在一起,此时记查询结果为 \(f_i\),再把顺序调换一下,改为 \(BA\)(内部顺序不变),记查询结果为 \(g_i\)。
\(A\) 中的点得到的贡献大小为 \((dg_i-g_i+f_i)\times 0+(g_i-f_i)\times 1\)。
\(B\) 中的点得到的贡献大小为 \((dg_i-g_i+f_i)\times 1+(g_i-f_i)\times 0\)。
然后乘上 \(2^k\) 就是在第 \(k\) 位上的总和。
计算一下次数,发现为 $2\lceil\log_2 n\rceil +2 $,就算 \(2\) 和 \(2\lceil\log_2 n\rceil\) 公用一个,也是 \(2\lceil\log_2 n\rceil+1=33\),超了一点。
此时,解决方法为考虑三进制。
如果可以做到一个三进制位只用查询 \(3\) 次的话,那么次数为 \(3\lceil\log_3 n\rceil+1=31\)。
假设我们当前考虑到了第 \(k\) 位,把第 \(k\) 位为 \(0\) 的数排成一个序列 \(A\),第 \(k\) 位为 \(1\) 的数排成一个序列 \(B\),第 \(k\) 位为 \(2\) 的数排成一个序列 \(C\)。
按照 \(ABC\) 的顺序拼接在一起,此时记查询结果为 \(f_i\),再改为 \(BCA\),记查询结果为 \(g_i\),再改为 \(CAB\),记查询结果为 \(h_i\)。
\(A\) 中的点得到的贡献大小为 \((dg_i-g_i+f_i)\times 0+(g_i-h_i)\times 1+(h_i-f_i)\times 2\)。
\(B\) 中的点得到的贡献大小为 \((f_i-g_i)\times 0+(dg_i-h_i+g_i)\times 1+(h_i-f_i)\times 2\)。
\(C\) 中的点得到的贡献大小为 \((f_i-g_i)\times 0+(g_i-h_i)\times 1+(dg_i-f_i+h_i)\times 2\)。
时间复杂度 \(O(n\log_3 n)\)。
#include <bits/stdc++.h>
using namespace std;
const int N=50005,M=35;
int n,dg[N],p[M][N],cnt,A[M][N],K;
int res[N],now[N],f[N],g[N],h[N],sum[N];
vector <int> bu[3];
inline void ask() {
cout<<K<<endl;
for (int j=1;j<=K;j++){
for (int i=1;i<=n;i++) cout<<p[j][i]<<" ";
cout<<endl;
}
for (int j=1;j<=K;j++){
for (int i=1;i<=n;i++) cin>>A[j][p[j][i]];
for (int i=n;i>=1;i--) A[j][p[j][i]]-=A[j][p[j][i-1]];
}
}
void dfs(int x){
int y=sum[x];sum[y]-=x;dg[x]--;
cout<<x<<" "<<y<<endl;
if (--dg[y]==1) dfs(y);
}
inline void work(){
cin>>n;K=0;
for (int i=1;i<=n;i++) sum[i]=0,now[i]=i;
for (int k=1,s=1;s<=n;k++,s*=3){
bu[0].clear(),bu[1].clear(),bu[2].clear();
for (int i=1;i<=n;i++) bu[now[i]%3].push_back(i),now[i]/=3;
++K,cnt=0;
for (int i:bu[0]) p[K][++cnt]=i;
for (int i:bu[1]) p[K][++cnt]=i;
for (int i:bu[2]) p[K][++cnt]=i;
++K,cnt=0;
for (int i:bu[1]) p[K][++cnt]=i;
for (int i:bu[2]) p[K][++cnt]=i;
for (int i:bu[0]) p[K][++cnt]=i;
++K,cnt=0;
for (int i:bu[2]) p[K][++cnt]=i;
for (int i:bu[0]) p[K][++cnt]=i;
for (int i:bu[1]) p[K][++cnt]=i;
}
++K;for (int i=1;i<=n;i++) p[K][i]=p[K-1][n-i+1];
ask();
for (int i=1;i<=n;i++) dg[i]=A[K][i]+A[K-1][i],now[i]=i;
K=0;
for (int k=1,s=1;s<=n;k++,s*=3){
bu[0].clear(),bu[1].clear(),bu[2].clear();
for (int i=1;i<=n;i++) bu[now[i]%3].push_back(i),now[i]/=3;
++K;for (int i=1;i<=n;i++) f[i]=A[K][i];
++K;for (int i=1;i<=n;i++) g[i]=A[K][i];
++K;for (int i=1;i<=n;i++) h[i]=A[K][i];
for (int i:bu[0]) sum[i]+=s*(g[i]-h[i]+(h[i]-f[i])*2);
for (int i:bu[1]) sum[i]+=s*(dg[i]-h[i]+g[i]+(h[i]-f[i])*2);
for (int i:bu[2]) sum[i]+=s*(g[i]-h[i]+(dg[i]-f[i]+h[i])*2);
}
for (int i=1;i<=n;i++) if (dg[i]==1) dfs(i);
}
int main(){
int T;cin>>T;
while (T--) work();
return 0;
}

浙公网安备 33010602011771号