一、题目
我们认为 \(x\rightarrow y\) 的简单路径是好的,当且仅当路径上的点最小的是 \(x\),最大的是 \(y\)
给出一棵 \(n\) 个点的树,求出好的简单路径条数。
\(n\leq 2\cdot 10^6\)
二、解法
很容易写出暴力点分治,但是因为需要解决二维偏序问题所以是 \(O(n\log^2n)\) 的。
首先考虑树是一条链的情况,发现可以二分求出 \(i\) 的范围,然后在这个范围中看有多少 \(j\) 即可。
那么搬到树上我们需要解决求出范围
这个问题,既然它又是路径最值问题我们可以使用重构树,我们从大到小加入节点,对于新加的节点 \(u\),我们看所有边 \((u,v)\) 并且 \(v\) 已经被加入了,那么我们合并 \(u\) 和 \(v\) 连通块的根,并且把 \(v\) 并查集的根设置成 \(u\),可以用并查集简单维护。
那么重构树上两个点的 \(\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);
}