Prufer 序列

发布时间 2023-08-06 20:22:07作者: SError

Tree \(\rightarrow\) Prufer

  • 每次找到编号最小的叶子结点,在序列中添加其父亲。

  • 删除该节点。

  • 重复如上操作,得到长为 \(n-2\) 的序列。

还原同理。

Prufer 序列是 \(n\) 个点的完全图的生成树与一个长为 \(n-2\),值域 \(\lbrack 1,n\rbrack\) 的数列构成的双射

P6086 【模板】Prüfer 序列

int f[N],p[N],d[N];
void Prufer(){
	for(int i=1;i<n;i++)
		f[i]=read(),d[f[i]]++;
	for(int i=1,j=1;i<=n-2;i++,j++){
		while(d[j])j++;
		p[i]=f[j];
		while(i<=n-2&&!(--d[p[i]])&&p[i]<j)
			p[i+1]=f[p[i]],i++;
	}
}
void Tree(){
	for(int i=1;i<=n-2;i++)
		p[i]=read(),d[p[i]]++;
	p[n-1]=n;
	for(int i=1,j=1;i<n;i++,j++){
		while(d[j])j++;
		f[j]=p[i];
		while(i<n&&!(--d[p[i]])&&p[i]<j)
			f[p[i]]=p[i+1],i++;
	}
}

  • prufer 序列与无根树一一对应。

  • 一个点的在 prufer 序列中的出现次数为其度数 \(-1\).

  • \(n\) 个点的完全图的生成树个数为 \(n^{n-2}\).

prufer 序列长为 \(n-2\),每一位都有 \(n\) 种可能性。

  • 度数为 \(d_{1\sim n}\) 的无根树共有 \(\displaystyle\frac{(n-2)!}{\prod_{i=1}^{n}(d_i-1)!}\) 种。

即可重全排列公式。

P2290 [HNOI2004] 树的计数

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>d[i],all+=d[i]-1;
	if(n==1){
		printf("%d\n",!d[1]);
		return 0;
	}
	if(all!=n-2){
		puts("0");return 0;
	}
	for(int i=1,sum=n-2;i<=n;i++)
		ans*=C[sum][d[i]-1],sum-=d[i]-1;
	printf("%lld\n",ans);
}

P2624 [HNOI2008] 明明的烦恼

一些点的度数不确定。\(1\le n\le 1000\).

记已知的个数为 \(k\)\(\sum d_i-1=s\).

答案为

\[\binom{n-2}{s}\cdot\frac{s!}{\prod_{i=1}^{k}(d_i-1)!}\cdot(n-k)^{n-2-s} \]

\[\large\frac{A_{n-2}^{s}\cdot(n-k)^{n-2-s}}{\prod_{i=1}^{k}(d_i-1)!} \]

牛马高精题。

洛阳七天下大雨同学附赠的 operator:

struct Sdate{
	vector<LL> mx;	
} A;
Sdate operator * (const Sdate A,const Sdate B){
	int l1=A.mx.size(),l2=B.mx.size(),l3=l1+l2-1;
	Sdate C; C.mx.resize(l1+l2-1);
	for(int i=0;i<l1;i++)
		for(int j=0;j<l2;j++)
			C.mx[i+j]+=A.mx[i]*B.mx[j];
	for(int i=0;i<l3;i++){
		if(i!=l3-1) C.mx[i+1]+=C.mx[i]/10;
		else if(C.mx[i]/10) C.mx.push_back(C.mx[i]/10),l3++;
		C.mx[i]%=10;
	}
	return C;
}

这下能做了。

暴力分解质因数最后乘起来。

写错了个逆天细节。


Clues

带标号无向图,记其有 \(k\) 个连通块,求用 \(k-1\) 条边使其连通的方案数,答案对 \(p\) 取模。

\(1\le n,m\le 10^5\)\(1\le p\le 10^9\).

等价缩点,记连通块 \(i\) 的点数为 \(s_i\),度数为 \(d_i\).

prufer 序列的总数为 \(\displaystyle\frac{(k-2)!}{\prod_{i=1}^{k}(d_i-1)!}\).

使连通需要再乘上 \(\displaystyle\prod_{i=1}^{k}s_i^{ d_i}\).

\(e_i=d_i-1\)

\[\sum_{\sum_{i=1}^{k}e_i=k-2}\binom{k-2}{e_1,e_2,\dots,e_k}\cdot\prod_{i=1}^{k}s_i^{e_i+1} \]

多元二项式定理,即

\[(s_1+\dots+s_k)^{k-2}\cdot\prod_{i=1}^{k}s_i \]

答案为

\[n^{k-2}\cdot\prod_{i=1}^{k}s_i \]

使用并查集维护 \(k\)\(s\).


P5437 【XR-2】约定

\(n\) 个点的无向完全图,\(i,j\) 间的边权为 \((i+j)^k\),求生成树的期望权值和。

答案对 \(998244353\) 取模。

\(1\le n\le 998244352\)\(1\le k\le 10^7\).

生成树共 \(n^{n-2}\) 个,共 \((n-1)n^{n-2}\) 条边,所以每条边出现 \(\displaystyle\frac{(n-1)n^{n-2}}{\frac{1}{2}n(n-1)}=2n^{n-3}\) 次。

答案为

\[\frac{2n^{n-3}}{n^{n-2}}\sum_{1\le i<j\le n}(i+j)^k=\frac{2}{n}\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(i+j)^k \]

\[a_n=\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}(i+j)^k \]

作差分得

\[a_n-a_{n-1}=\sum_{i=n+1}^{2n-1}i^k \]

容易发现 \(a_n\) 应该是关于 \(n\)\(k+2\) 次多项式。

那么求出暴力前 \(k+3\) 项和插值都可以做到 \(O(k)\).

实际上我疏忽了一个问题。。。\(O(n)\) 拉插的逆元其实是多带了一只 \(\log\) 的,所以模板题里面有可能只实现了 \(O(n^2)\) 转移到 \(O(n\log P)\).

为了优化这个逆元的常数,可以使用 P5431 【模板】乘法逆元 2 的套路:

假设当前要对 \(\{a\}\)\(p\) 求逆,令 \(\displaystyle A=\prod_{i=1}^{n}a_i\),求出 \(A^{-1}\),那么有

\[a_i^{-1}=A^{-1}\cdot\prod_{j\not=i}a_j \]

维护前、后缀积即可。

最终答案为 \(\displaystyle\frac{2}{n}\cdot a_n\),时间复杂度 \(O(k)\).

附一份代码吧。

void init(){
	f[1]=fac[0]=1;
	for(int i=2;i<(R<<1);i++){
		if(!vis[i])p[++cnt]=i,f[i]=qpow(i,k);
		for(int j=1;j<=cnt&&i*p[j]<(R<<1);j++){
			vis[i*p[j]]=true,f[i*p[j]]=1ll*f[i]*f[p[j]]%P;
			if(i%p[j]==0)break;
		}
	}
	for(int i=1;i<(R<<1);i++)
		(f[i]+=f[i-1])%=P;
	for(int i=1;i<=R;i++){
		a[i]=((a[i-1]+f[i*2-1]-f[i])%P+P)%P;
		fac[i]=1ll*fac[i-1]*i%P;
	}
}
int b[N<<1],suf[N<<1],tot;
int calc(int n){
	if(n<=R)return a[n];
	int A=1,ret=0,r1,r2;
	for(int i=1;i<=R;i++){
		A=1ll*A*(n-i)%P,b[++tot]=n-i;
		b[++tot]=(1ll*fac[i-1]*fac[R-i]%P*((R-i)&1?-1:1)+P)%P;
	}
	suf[tot+1]=1;
	for(int i=tot;i;i--)
		suf[i]=1ll*suf[i+1]*b[i]%P;
	suf[0]=qpow(suf[1],P-2);
	for(int i=1,pref=1,tp;i<=tot;i++){
		tp=b[i];
		b[i]=1ll*suf[0]*suf[i+1]%P*pref%P;
		pref=1ll*pref*tp%P;
	}
	for(int i=1;i<=R;i++){
		r1=1ll*A*b[i*2-1]%P,r2=b[i*2];
		(ret+=1ll*a[i]*r1%P*r2%P)%=P;
	}
	return ret;
}
int main(){
	R=k+3;
	init();
	ans=2ll*calc(n)%P*qpow(n,P-2)%P;
	
	return 0;
}