1. 程式人生 > 其它 >#輕重鏈剖分,互動#LOJ 6669 Nauuo and Binary Tree

#輕重鏈剖分,互動#LOJ 6669 Nauuo and Binary Tree

題目

有一棵大小為\(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\)

得到它們的\(LCA\)

若當前想要找的是\(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;
}