[提高组集训2021] 超级加倍

一、题目

我们认为 \(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);
}
posted @ 2021-11-10 21:28  C202044zxy  阅读(90)  评论(2编辑  收藏  举报