[提高組集訓2021] 超級加倍
阿新 • • 發佈:2021-11-10
一、題目
我們認為 \(x\rightarrow y\) 的簡單路徑是好的,當且僅當路徑上的點最小的是 \(x\),最大的是 \(y\)
給出一棵 \(n\) 個點的樹,求出好的簡單路徑條數。
\(n\leq 2\cdot 10^6\)
二、解法
很容易寫出暴力點分治,但是因為需要解決二維偏序問題所以是 \(O(n\log^2n)\) 的。
首先考慮樹是一條鏈的情況,發現可以二分求出 \(i\) 的範圍,然後在這個範圍中看有多少 \(j\) 即可。
那麼搬到樹上我們需要解決求出範圍
這個問題,既然它又是路徑最值問題我們可以使用重構樹,我們從大到小加入節點,對於新加的節點 \(u\),我們看所有邊 \((u,v)\)
那麼重構樹上兩個點的 \(\tt lca\) 就是它們真實路徑上的最小值,用類似的方法可以求出第二棵樹使得 \(\tt lca\) 就是它們真實路徑上的最大值。那麼條件轉化成第一棵樹上 \(x\) 是 \(y\) 的祖先,第二棵樹上 \(y\) 是 \(x\) 的祖先,我們可以求出第一棵樹上的 \(\tt dfn\) 序,在第二棵樹上 \(\tt dfs\),用樹狀陣列來統計答案,時間複雜度 \(O(n\log n)\)
三、總結
對重構樹的理解不僅僅是最小生成樹上的邊重構,還可以是本題的點重構。總之就是解決一個範圍的問題,也就是滿足某一條件的點在樹上有特定的範圍(比如子樹)
//I just gonna burn the stars... #include <cstdio> #include <vector> #include <iostream> #include <algorithm> using namespace std; const int M = 2000005; int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int n,m,tot,t1,t2,rt[2],a[M],b[M],f[M],f1[M],f2[M]; int fa[M],vis[M],in[M],out[M];long long ans; struct edge{int v,next;}e[M<<1],e1[M],e2[M]; int find(int x) { if(x!=fa[x]) fa[x]=find(fa[x]); return fa[x]; } void build(int F) { for(int i=1;i<=n;i++) fa[i]=i,vis[i]=0; for(int i=1;i<=n;i++) { int u=a[i];vis[u]=1; for(int j=f[u];j;j=e[j].next) { int v=e[j].v; if(vis[v]) { if(F==0) e1[++t1]=edge{find(v),f1[u]},f1[u]=t1; else e2[++t2]=edge{find(v),f2[u]},f2[u]=t2; fa[find(v)]=u; } } } rt[F]=find(1); } void dfs1(int u) { in[u]=++m; for(int i=f1[u];i;i=e1[i].next) dfs1(e1[i].v); out[u]=m; } int lowbit(int x) { return x&(-x); } void add(int x,int c) { for(int i=x;i<=n;i+=lowbit(i)) b[i]+=c; } int ask(int x) { int r=0; for(int i=x;i>0;i-=lowbit(i)) r+=b[i]; return r; } void dfs2(int u) { add(in[u],1); ans+=ask(out[u])-ask(in[u]); for(int i=f2[u];i;i=e2[i].next) dfs2(e2[i].v); add(in[u],-1); } signed main() { freopen("charity.in","r",stdin); freopen("charity.out","w",stdout); n=read(); for(int i=1;i<=n;i++) { a[i]=i; int j=read();if(!j) continue; e[++tot]=edge{i,f[j]},f[j]=tot; e[++tot]=edge{j,f[i]},f[i]=tot; } build(0);reverse(a+1,a+1+n); build(1);dfs1(rt[0]);dfs2(rt[1]); printf("%lld\n",ans); }