Cây là cấu trúc dữ liệu phi tuyến tính — các đỉnh liên kết nhau theo dạng phân cấp, không có chỉ số đánh tuyến tính như mảng. Điều này khiến việc truy vấn trên cây (tổng subtree, cập nhật đường đi, tìm LCA) trở nên khó khăn nếu xử lý trực tiếp.
Vấn đề cốt lõi: Làm sao biến một cấu trúc cây thành một cấu trúc tuyến tính (mảng) để có thể dùng các cấu trúc dữ liệu quen thuộc như BIT, Segment Tree, Sparse Table?
Euler Tour là kỹ thuật giải quyết chính xác vấn đề này. Bằng cách duyệt DFS và ghi lại thứ tự thăm, ta "trải phẳng" cây thành mảng sao cho các tập hợp đỉnh có ý nghĩa (subtree, đường đi) trở thành các đoạn liên tục hoặc có thể xử lý bằng mảng hiệu.
Với cây có \(N \leq 10^5\) đỉnh và \(Q \leq 10^5\) truy vấn:
Khi duyệt DFS trên cây, mỗi đỉnh được "vào" một lần và "ra" một lần. Ghi lại hai thời điểm này ta được cặp giá trị \((tin[u], tout[u])\) cho mỗi đỉnh \(u\).
Tính chất then chốt: Tất cả đỉnh trong subtree của \(u\) tạo thành đoạn liên tục \([tin[u], tout[u]]\) trong mảng Euler Tour.
#include<bits/stdc++.h>usingnamespacestd;constintMAXN=200005;vector<int>adj[MAXN];// danh sách kềinttin[MAXN],tout[MAXN];// thời gian vào/ra của mỗi đỉnhinttimer_dfs=0;// bộ đếm thời gian DFSvoiddfs(intu,intparent){tin[u]=++timer_dfs;// ghi nhận thời gian vàofor(intv:adj[u]){if(v!=parent){dfs(v,u);// đệ quy xuống con}}tout[u]=timer_dfs;// ghi nhận thời gian ra}boolis_ancestor(intu,intv){returntin[u]<=tin[v]&&tout[v]<=tout[u];// kiểm tra u có là tổ tiên của v}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);intn,q;cin>>n>>q;for(inti=0;i<n-1;i++){intu,v;cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}dfs(1,0);// bắt đầu DFS từ đỉnh 1while(q--){intu;cin>>u;cout<<tout[u]-tin[u]+1<<"\n";// kích thước subtree của u}return0;}
importsyssys.setrecursionlimit(300000)defdfs(u,parent):globaltimer_dfstimer_dfs+=1tin[u]=timer_dfs# ghi nhận thời gian vàoforvinadj[u]:ifv!=parent:dfs(v,u)# đệ quy xuống contout[u]=timer_dfs# ghi nhận thời gian radefis_ancestor(u,v):returntin[u]<=tin[v]andtout[v]<=tout[u]# kiểm tra u có là tổ tiên của vn,q=map(int,input().split())adj=[[]for_inrange(n+1)]tin=[0]*(n+1)tout=[0]*(n+1)timer_dfs=0for_inrange(n-1):u,v=map(int,input().split())adj[u].append(v)adj[v].append(u)dfs(1,0)# bắt đầu DFS từ đỉnh 1for_inrange(q):u=int(input())print(tout[u]-tin[u]+1)# kích thước subtree của u
Bài toán: Cho cây \(N\) đỉnh, mỗi đỉnh có giá trị. Xử lý hai loại truy vấn:
UPDATE u val: Gán giá trị đỉnh \(u = val\)
QUERY u: Tính tổng giá trị các đỉnh trong subtree của \(u\)
Ý tưởng: Duyệt Euler Tour Loại 1, lưu giá trị đỉnh \(u\) tại vị trí \(tin[u]\) trong mảng BIT. Subtree của \(u\) là đoạn \([tin[u], tout[u]]\), dùng BIT để tính tổng đoạn.
#include<bits/stdc++.h>usingnamespacestd;constintMAXN=200005;vector<int>adj[MAXN];// danh sách kềinttin[MAXN],tout[MAXN];// thời gian vào/ralonglongval[MAXN];// giá trị các đỉnhlonglongbit[MAXN];// BIT lưu tổnginttimer_dfs=0;intn,q;voidupdate(inti,longlongdelta){for(;i<=n;i+=i&(-i))bit[i]+=delta;// cập nhật BIT tại vị trí i}longlongquery(inti){longlongsum=0;for(;i>0;i-=i&(-i))sum+=bit[i];// lấy tổng prefix [1..i]returnsum;}longlongrange_query(intl,intr){returnquery(r)-query(l-1);// tổng đoạn [l, r]}voiddfs(intu,intparent){tin[u]=++timer_dfs;// ghi nhận thời gian vàofor(intv:adj[u]){if(v!=parent){dfs(v,u);}}tout[u]=timer_dfs;// ghi nhận thời gian ra}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);cin>>n>>q;for(inti=1;i<=n;i++)cin>>val[i];for(inti=0;i<n-1;i++){intu,v;cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}dfs(1,0);// DFS Euler Tourfor(inti=1;i<=n;i++){update(tin[i],val[i]);// đưa giá trị đỉnh vào BIT}while(q--){inttype;cin>>type;if(type==1){intu;longlongnew_val;cin>>u>>new_val;longlongdelta=new_val-val[u];val[u]=new_val;update(tin[u],delta);// cập nhật giá trị đỉnh u}else{intu;cin>>u;cout<<range_query(tin[u],tout[u])<<"\n";// tổng subtree u}}return0;}
importsyssys.setrecursionlimit(300000)defupdate(i,delta):whilei<=n:bit[i]+=delta# cập nhật BIT tại vị trí ii+=i&(-i)defquery(i):s=0whilei>0:s+=bit[i]# lấy tổng prefix [1..i]i-=i&(-i)returnsdefrange_query(l,r):returnquery(r)-query(l-1)# tổng đoạn [l, r]defdfs(u,parent):globaltimer_dfstimer_dfs+=1tin[u]=timer_dfs# ghi nhận thời gian vàoforvinadj[u]:ifv!=parent:dfs(v,u)tout[u]=timer_dfs# ghi nhận thời gian rainput_data=sys.stdin.read().split()idx=0n=int(input_data[idx]);idx+=1q=int(input_data[idx]);idx+=1val=[0]*(n+1)foriinrange(1,n+1):val[i]=int(input_data[idx]);idx+=1adj=[[]for_inrange(n+1)]for_inrange(n-1):u=int(input_data[idx]);idx+=1v=int(input_data[idx]);idx+=1adj[u].append(v)adj[v].append(u)tin=[0]*(n+1)tout=[0]*(n+1)timer_dfs=0dfs(1,0)# DFS Euler Tourbit=[0]*(n+1)foriinrange(1,n+1):update(tin[i],val[i])# đưa giá trị đỉnh vào BITout=[]for_inrange(q):t=int(input_data[idx]);idx+=1ift==1:u=int(input_data[idx]);idx+=1new_val=int(input_data[idx]);idx+=1delta=new_val-val[u]val[u]=new_valupdate(tin[u],delta)# cập nhật giá trị đỉnh uelse:u=int(input_data[idx]);idx+=1out.append(str(range_query(tin[u],tout[u])))# tổng subtree uprint("\n".join(out))
Bài toán 3: Truy vấn min/max trên subtree bằng Segment Tree¶
Tương tự BIT, nhưng thay vì tính tổng, ta lưu giá trị nhỏ nhất (hoặc lớn nhất) trên đoạn.
#include<bits/stdc++.h>usingnamespacestd;constintMAXN=200005;constlonglongINF=1e18;vector<int>adj[MAXN];// danh sách kềinttin[MAXN],tout[MAXN];// thời gian vào/ralonglongval[MAXN];// giá trị các đỉnhlonglongtree[4*MAXN];// Segment Treeinttimer_dfs=0;intn;voidbuild(intnode,intstart,intend){if(start==end){tree[node]=INF;// khởi tạo láreturn;}intmid=(start+end)/2;build(2*node,start,mid);build(2*node+1,mid+1,end);tree[node]=min(tree[2*node],tree[2*node+1]);// lấy min hai con}voidupdate(intnode,intstart,intend,intpos,longlongnew_val){if(start==end){tree[node]=new_val;// cập nhật láreturn;}intmid=(start+end)/2;if(pos<=mid)update(2*node,start,mid,pos,new_val);elseupdate(2*node+1,mid+1,end,pos,new_val);tree[node]=min(tree[2*node],tree[2*node+1]);// cập nhật min}longlongquery(intnode,intstart,intend,intl,intr){if(r<start||end<l)returnINF;// ngoài đoạnif(l<=start&&end<=r)returntree[node];// nằm hoàn toàn trong đoạnintmid=(start+end)/2;returnmin(query(2*node,start,mid,l,r),query(2*node+1,mid+1,end,l,r));}voiddfs(intu,intparent){tin[u]=++timer_dfs;// ghi nhận thời gian vàofor(intv:adj[u]){if(v!=parent)dfs(v,u);}tout[u]=timer_dfs;// ghi nhận thời gian ra}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);cin>>n;for(inti=1;i<=n;i++)cin>>val[i];for(inti=0;i<n-1;i++){intu,v;cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}dfs(1,0);// DFS Euler Tourbuild(1,1,n);// xây Segment Treefor(inti=1;i<=n;i++){update(1,1,n,tin[i],val[i]);// đưa giá trị vào cây}intq;cin>>q;while(q--){inttype;cin>>type;if(type==1){intu;longlongnew_val;cin>>u>>new_val;val[u]=new_val;update(1,1,n,tin[u],new_val);// cập nhật giá trị}else{intu;cin>>u;cout<<query(1,1,n,tin[u],tout[u])<<"\n";// min subtree u}}return0;}
importsyssys.setrecursionlimit(300000)INF=float('inf')defbuild(node,start,end):ifstart==end:tree[node]=INF# khởi tạo láelse:mid=(start+end)//2build(2*node,start,mid)build(2*node+1,mid+1,end)tree[node]=min(tree[2*node],tree[2*node+1])# lấy min hai condefupdate(node,start,end,pos,new_val):ifstart==end:tree[node]=new_val# cập nhật láelse:mid=(start+end)//2ifpos<=mid:update(2*node,start,mid,pos,new_val)else:update(2*node+1,mid+1,end,pos,new_val)tree[node]=min(tree[2*node],tree[2*node+1])# cập nhật mindefquery(node,start,end,l,r):ifr<startorend<l:# ngoài đoạnreturnINFifl<=startandend<=r:# nằm hoàn toàn trong đoạnreturntree[node]mid=(start+end)//2returnmin(query(2*node,start,mid,l,r),query(2*node+1,mid+1,end,l,r))defdfs(u,parent):globaltimer_dfstimer_dfs+=1tin[u]=timer_dfs# ghi nhận thời gian vàoforvinadj[u]:ifv!=parent:dfs(v,u)tout[u]=timer_dfs# ghi nhận thời gian rainput_data=sys.stdin.read().split()idx=0n=int(input_data[idx]);idx+=1val=[0]*(n+1)foriinrange(1,n+1):val[i]=int(input_data[idx]);idx+=1adj=[[]for_inrange(n+1)]for_inrange(n-1):u=int(input_data[idx]);idx+=1v=int(input_data[idx]);idx+=1adj[u].append(v)adj[v].append(u)tin=[0]*(n+1)tout=[0]*(n+1)timer_dfs=0dfs(1,0)# DFS Euler Tourtree=[0]*(4*n+5)build(1,1,n)# xây Segment Treeforiinrange(1,n+1):update(1,1,n,tin[i],val[i])# đưa giá trị vào câyq=int(input_data[idx]);idx+=1out=[]for_inrange(q):t=int(input_data[idx]);idx+=1ift==1:u=int(input_data[idx]);idx+=1new_val=int(input_data[idx]);idx+=1update(1,1,n,tin[u],new_val)# cập nhật giá trịelse:u=int(input_data[idx]);idx+=1out.append(str(query(1,1,n,tin[u],tout[u])))# min subtree uprint("\n".join(out))
Bài toán 4: Cập nhật subtree bằng Euler Tour Type 2 + Mảng hiệu¶
Bài toán: Cho cây \(N\) đỉnh. Xử lý hai loại truy vấn:
UPDATE u val: Cộng \(val\) vào tất cả đỉnh trong subtree của \(u\)
QUERY u: Truy vấn giá trị tại đỉnh \(u\)
Ý tưởng: Dùng Euler Tour Loại 2 (entry + exit). Mỗi đỉnh xuất hiện 2 lần. Dùng mảng hiệu (difference array):
Cập nhật subtree \(u\) với giá trị \(val\): cộng \(val\) tại \(tin[u]\), trừ \(val\) tại \(tout[u] + 1\)
Truy vấn đỉnh \(u\): lấy tổng tiền tố tại \(tin[u]\)
Điều này hoạt động vì tất cả vị trí trong phạm vi \([tin[u], tout[u]]\) đều nhận được \(val\), còn các vị trí ngoài phạm vi thì không (hiệu triệt tiêu nhau).
#include<bits/stdc++.h>usingnamespacestd;constintMAXN=200005;vector<int>adj[MAXN];// danh sách kềinttin[MAXN],tout[MAXN];// thời gian vào/ra (Type 2: 2 lần)longlongbit[2*MAXN];// BIT cho mảng hiệuinttimer_dfs=0;intn,q;voidupdate(inti,longlongdelta){for(;i<=2*n;i+=i&(-i))bit[i]+=delta;// cập nhật BIT}longlongquery(inti){longlongsum=0;for(;i>0;i-=i&(-i))sum+=bit[i];// lấy tổng prefixreturnsum;}voiddfs(intu,intparent){tin[u]=++timer_dfs;// ghi nhận thời gian vàofor(intv:adj[u]){if(v!=parent){dfs(v,u);}}tout[u]=++timer_dfs;// ghi nhận thời gian ra (tăng thêm 1)}voidupdate_subtree(intu,longlongval){update(tin[u],val);// +val tại tin[u]update(tout[u]+1,-val);// -val tại tout[u]+1}longlongpoint_query(intu){returnquery(tin[u]);// tổng prefix tại tin[u]}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);cin>>n>>q;for(inti=0;i<n-1;i++){intu,v;cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}dfs(1,0);// DFS Euler Tour Type 2for(inti=1;i<=n;i++){longlongval;cin>>val;update_subtree(i,val);// khởi tạo giá trị ban đầu}while(q--){inttype;cin>>type;if(type==1){intu;longlongval;cin>>u>>val;update_subtree(u,val);// cộng val vào subtree u}else{intu;cin>>u;cout<<point_query(u)<<"\n";// truy vấn giá trị đỉnh u}}return0;}
importsyssys.setrecursionlimit(300000)defupdate(i,delta):whilei<=2*n:bit[i]+=delta# cập nhật BITi+=i&(-i)defquery(i):s=0whilei>0:s+=bit[i]# lấy tổng prefixi-=i&(-i)returnsdefdfs(u,parent):globaltimer_dfstimer_dfs+=1tin[u]=timer_dfs# ghi nhận thời gian vàoforvinadj[u]:ifv!=parent:dfs(v,u)timer_dfs+=1tout[u]=timer_dfs# ghi nhận thời gian ra (tăng thêm 1)defupdate_subtree(u,val):update(tin[u],val)# +val tại tin[u]update(tout[u]+1,-val)# -val tại tout[u]+1defpoint_query(u):returnquery(tin[u])# tổng prefix tại tin[u]input_data=sys.stdin.read().split()idx=0n=int(input_data[idx]);idx+=1q=int(input_data[idx]);idx+=1adj=[[]for_inrange(n+1)]for_inrange(n-1):u=int(input_data[idx]);idx+=1v=int(input_data[idx]);idx+=1adj[u].append(v)adj[v].append(u)tin=[0]*(n+1)tout=[0]*(n+1)bit=[0]*(2*n+5)timer_dfs=0dfs(1,0)# DFS Euler Tour Type 2foriinrange(1,n+1):val=int(input_data[idx]);idx+=1update_subtree(i,val)# khởi tạo giá trị ban đầuout=[]for_inrange(q):t=int(input_data[idx]);idx+=1ift==1:u=int(input_data[idx]);idx+=1val=int(input_data[idx]);idx+=1update_subtree(u,val)# cộng val vào subtree uelse:u=int(input_data[idx]);idx+=1out.append(str(point_query(u)))# truy vấn giá trị đỉnh uprint("\n".join(out))
#include<bits/stdc++.h>usingnamespacestd;constintMAXN=200005;constintLOG=20;vector<int>adj[MAXN];intdepth[MAXN];// độ sâu của mỗi đỉnhinteuler[2*MAXN];// mảng Euler Tour (ghi đỉnh mỗi lần thăm)intfirst[MAXN];// vị trí đầu tiên đỉnh u xuất hiệninteuler_depth[2*MAXN];// độ sâu tương ứng với mảng eulerintst[2*MAXN][LOG];// Sparse Table cho RMQintlog_table[2*MAXN];// bảng logintn,q,euler_cnt;// số đỉnh, truy vấn, bộ đếmvoiddfs(intu,intparent,intd){depth[u]=d;euler[euler_cnt]=u;// ghi đỉnh vào mảng Eulereuler_depth[euler_cnt]=d;if(first[u]==-1)first[u]=euler_cnt;// lưu vị trí đầu tiêneuler_cnt++;for(intv:adj[u]){if(v!=parent){dfs(v,u,d+1);euler[euler_cnt]=u;// ghi lại khi quay luieuler_depth[euler_cnt]=d;euler_cnt++;}}}voidbuild_sparse_table(){intm=euler_cnt;log_table[1]=0;for(inti=2;i<=m;i++)log_table[i]=log_table[i/2]+1;for(inti=0;i<m;i++)st[i][0]=i;// khởi tạo độ dài 1for(intj=1;(1<<j)<=m;j++){for(inti=0;i+(1<<j)-1<m;i++){intleft=st[i][j-1];intright=st[i+(1<<(j-1))][j-1];st[i][j]=(euler_depth[left]<euler_depth[right])?left:right;// lấy chỉ số depth nhỏ hơn}}}intquery_rmq(intl,intr){intk=log_table[r-l+1];intleft=st[l][k];intright=st[r-(1<<k)+1][k];return(euler_depth[left]<euler_depth[right])?left:right;// chỉ số depth nhỏ hơn}intlca(intu,intv){intl=first[u],r=first[v];if(l>r)swap(l,r);intidx=query_rmq(l,r);// RMQ trên đoạn [first[u], first[v]]returneuler[idx];// đỉnh có depth nhỏ nhất là LCA}intmain(){ios_base::sync_with_stdio(false);cin.tie(nullptr);cin>>n>>q;for(inti=0;i<n-1;i++){intu,v;cin>>u>>v;adj[u].push_back(v);adj[v].push_back(u);}memset(first,-1,sizeof(first));euler_cnt=0;dfs(1,0,0);// DFS từ gốc 1build_sparse_table();// xây Sparse Tablewhile(q--){intu,v;cin>>u>>v;cout<<lca(u,v)<<"\n";// in LCA}return0;}
importsyssys.setrecursionlimit(300000)defdfs(u,parent,d):globaleuler_cntdepth[u]=deuler[euler_cnt]=u# ghi đỉnh vào mảng Eulereuler_depth[euler_cnt]=diffirst[u]==-1:first[u]=euler_cnt# lưu vị trí đầu tiêneuler_cnt+=1forvinadj[u]:ifv!=parent:dfs(v,u,d+1)euler[euler_cnt]=u# ghi lại khi quay luieuler_depth[euler_cnt]=deuler_cnt+=1defbuild_sparse_table():m=euler_cntlog_table[1]=0foriinrange(2,m+1):log_table[i]=log_table[i//2]+1foriinrange(m):st[i][0]=i# khởi tạo độ dài 1j=1while(1<<j)<=m:i=0whilei+(1<<j)-1<m:left=st[i][j-1]right=st[i+(1<<(j-1))][j-1]st[i][j]=leftifeuler_depth[left]<euler_depth[right]elseright# chỉ số depth nhỏ hơni+=1j+=1defquery_rmq(l,r):k=log_table[r-l+1]left=st[l][k]right=st[r-(1<<k)+1][k]returnleftifeuler_depth[left]<euler_depth[right]elserightdeflca(u,v):l,r=first[u],first[v]ifl>r:l,r=r,lidx=query_rmq(l,r)# RMQ trên đoạn [first[u], first[v]]returneuler[idx]# đỉnh có depth nhỏ nhất là LCAinput_data=sys.stdin.read().split()idx=0n=int(input_data[idx]);idx+=1q=int(input_data[idx]);idx+=1adj=[[]for_inrange(n+1)]for_inrange(n-1):u=int(input_data[idx]);idx+=1v=int(input_data[idx]);idx+=1adj[u].append(v)adj[v].append(u)depth=[0]*(n+1)euler=[0]*(2*n)euler_depth=[0]*(2*n)first=[-1]*(n+1)log_table=[0]*(2*n+1)st=[[0]*20for_inrange(2*n)]euler_cnt=0dfs(1,0,0)# DFS từ gốc 1build_sparse_table()# xây Sparse Tableout=[]for_inrange(q):u=int(input_data[idx]);idx+=1v=int(input_data[idx]);idx+=1out.append(str(lca(u,v)))# in LCAprint("\n".join(out))
Cần chứng minh: Với mọi đỉnh \(u\), tất cả đỉnh trong subtree của \(u\) có \(tin\) nằm trong đoạn \([tin[u], tout[u]]\), và không đỉnh nào ngoài subtree có \(tin\) trong đoạn này.
Chứng minh: Trong DFS, khi thăm \(u\), ta ghi \(tin[u]\). Sau đó đệ quy thăm tất cả con \(v_1, v_2, \dots, v_k\). Mỗi con \(v_i\) và toàn bộ subtree của \(v_i\) được thăm xong trước khi chuyển sang con tiếp theo. Cuối cùng ghi \(tout[u]\).
Do đó:
- Mọi đỉnh \(w\) trong subtree \(u\): \(tin[u] \leq tin[w] \leq tout[w] \leq tout[u]\)
- Mọi đỉnh \(w\) ngoài subtree \(u\): hoặc \(tin[w] < tin[u]\) hoặc \(tin[w] > tout[u]\)
Suy ra subtree \(u\) ứng chính xác với đoạn \([tin[u], tout[u]]\).
Tính chất: Mảng hiệu cho cập nhật subtree (Type 2)¶
Khi dùng Euler Tour Type 2 (entry + exit), mỗi đỉnh \(u\) có \(tin[u]\) (lần vào) và \(tout[u]\) (lần ra). Dãy Euler Tour có \(2N\) phần tử.
Khi cập nhật subtree \(u\) với giá trị \(val\):
- Cộng \(val\) tại vị trí \(tin[u]\)
- Trừ \(val\) tại vị trí \(tout[u] + 1\)
Tổng tiền tố tại vị trí \(p\) sẽ nhận được \(val\) khi và chỉ khi \(tin[u] \leq p \leq tout[u]\). Điều này đúng vì:
- Nếu \(p < tin[u]\): chưa gặp \(+val\) nên tổng tiền tố không đổi
- Nếu \(tin[u] \leq p \leq tout[u]\): đã gặp \(+val\) nhưng chưa gặp \(-val\), nên tổng tiền tố cộng thêm \(val\)
- Nếu \(p > tout[u]\): đã gặp cả \(+val\) và \(-val\), hiệu triệt tiêu nhau
\(E\) là mảng Euler Tour DFS (ghi đỉnh mỗi lần thăm, kể cả quay lại). \(first[u]\) là vị trí đầu tiên \(u\) xuất hiện trong \(E\).
Cần chứng minh:\(LCA(u, v)\) = đỉnh có depth nhỏ nhất trong đoạn \(E[\min(first[u], first[v]) \dots \max(first[u], first[v])]\).
Chứng minh: Khi DFS từ \(first[u]\) đến \(first[v]\), đường đi trong cây đi từ \(u\) lên \(LCA(u,v)\) rồi xuống \(v\). Đỉnh có depth nhỏ nhất trên đường này chính là \(LCA(u,v)\). Mọi đỉnh khác trong đoạn \(E\) đều có depth lớn hơn vì chúng nằm trong các nhánh phụ được thăm xen kẽ.
Nhầm \(tout[u] + 1\) trong Type 2: Phải dùng update(tout[u] + 1, -val), không phải update(tout[u], -val). Nếu dùng \(tout[u]\), chính đỉnh \(u\) sẽ bị trừ mất giá trị.
1-indexed vs 0-indexed: BIT thường dùng 1-indexed (\(i > 0\) trong vòng lặp). Cần nhất quán giữa chỉ số Euler Tour và BIT.
Quên reset timer: Khi chạy nhiều test case, phải đặt lại timer_dfs = 0 trước mỗi test.
Stack overflow: Cây dạng dây (chain) với \(N = 2 \times 10^5\) gây tràn stack. Giải pháp: dùng iterative DFS trong C++, hoặc sys.setrecursionlimit(300000) trong Python.
Sparse Table so sánh sai: Phải so sánh \(euler\_depth\), không so sánh chỉ số mảng.