#輕重鏈剖分,互動#LOJ 6669 Nauuo and Binary Tree
阿新 • • 發佈:2021-06-21
題目
有一棵大小為\(n\)只知道根節點為1的二叉樹,
可以不超過\(3*10^4\)詢問兩點之間距離,
最後輸出除了點1以外其餘點的祖先
\(n\leq 3000\)
分析
\(O(n^2)\)的時間複雜度就可以了,主要是控制詢問次數
首先將所有點的深度求出來,
那麼詢問按照點的深度遞增去找該點的祖先,
有一個很重要的性質就是
通過詢問可以將\(dep[x]+dep[y]-2*dep[LCA]\)求出來,由於\(dep[x]+dep[y]\)是確定的,那麼\(LCA\)也可以求出來
由於\(dep[x],dep[y]\geq dep[LCA]\)所以只要按照深度一層一層找就可以詢問\(x,y\)
若當前想要找的是\(x\)的祖先,那麼只要找到一個合適的\(y\)讓\(dep[x]=dep[LCA]+1\)即可以讓\(LCA\)變成\(x\)的祖先,
那麼一定讓重新找\(y\)的次數最少,考慮樹上的每條路徑都可以被拆分成不超過\(O(\log n)\)條重鏈。
從根節點開始每次找重鏈然後如果LCA所對應的輕兒子存在那就跳到輕兒子,則總詢問次數為\(O(n+n\log n)\)實際更小
程式碼
#include <cstdio> #include <cctype> #include <algorithm> #define rr register using namespace std; const int N=3011; int siz[N],son[N][2],foot[N],n,rk[N],dep[N],fat[N]; inline signed iut(){ rr int ans=0; rr char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; } inline void print(int ans){ if (ans>9) print(ans/10); putchar(ans%10+48); } bool cmp(int x,int y){return dep[x]<dep[y];} inline signed Ask(int x,int y){ putchar(63),putchar(32),print(x),putchar(32), print(y),putchar(10),fflush(stdout); return iut(); } inline void dfs1(int x){ siz[x]=1,foot[x]=x; if (son[x][0]) dfs1(son[x][0]),siz[x]+=siz[son[x][0]]; if (son[x][1]) dfs1(son[x][1]),siz[x]+=siz[son[x][1]]; if (siz[son[x][0]]<siz[son[x][1]]) swap(son[x][0],son[x][1]); if (son[x][0]) foot[x]=foot[son[x][0]]; } inline void dfs2(int x,int y){ rr int now=foot[x],d=Ask(now,y); while (dep[now]>(dep[foot[x]]+dep[y]-d)/2) now=fat[now]; if (son[now][1]) dfs2(son[now][1],y); else{ fat[y]=now; if (son[now][0]) son[now][1]=y; else son[now][0]=y; } } signed main(){ n=iut(); for (rr int i=2;i<=n;++i) dep[i]=Ask(1,i),rk[i]=i; sort(rk+2,rk+1+n,cmp),fat[rk[2]]=1,son[1][0]=rk[2]; for (rr int i=3;i<=n;++i) dfs1(1),dfs2(1,rk[i]); putchar(33); for (rr int i=2;i<=n;++i) putchar(32),print(fat[i]); putchar(10),fflush(stdout); return 0; }