1. 程式人生 > 其它 >[提高組集訓2021] 超級加倍

[提高組集訓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);
}