[提高组集训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);
}
上一篇:[CF321E] Ciel and Gondolas


下一篇:单调栈