[正睿集训2021] 模拟赛2

计算器

咕咕咕

二叉树

题目描述

有一棵无穷大的完全二叉树 \(T\),任意一个点都有两个子节点。给定一棵有限的二叉树 \(G\),满足除叶节点外每个节点都有恰好两个儿子,你需要将 \(G\) 的每个节点分别对应到 \(T\) 的节点上面去,使得对于 \(G\) 的叶节点深度为 \(h[i]\),对于 \(G\) 的非叶节点的两个子节点 \(ls,rs\),需要分别在 \(T\) 的对应节点的左右子树上。

求对应的方案数,对 \(1e9+7\) 取模。

\(n\leq 5000,h_i\leq 10^9\)

解法

我们考虑 \(G\),可以在树上 \(dp\),设 \(f[i][j]\) 表示点 \(i\) 被放在 \(T\)\(j\) 深度上,并考虑其子树的放置方案数,转移就枚举左右儿子放置的深度:

\[f(i,j)=(\sum_{k>j}f(l,k)2^{k-j-1})(\sum_{k>j}f(r,k)2^{k-j-1}) \]

\(g(i,j)=\sum_{k\geq j}f(i,k)2^k\),则 \(dp\) 方程可以写成 \(f(i,j)=g(l,j+1)g(r,j+1)2^{-2j-2}\),这样算法就是 \(O(nh_i)\) 的。

后面真的是神仙操作了,我们想沿用 \(dp\) 的思路,但是奈何深度这一维太大了。这时候我们可以拿出多项式来优化,我们一般是把范围比较大的信息设置为多项式的变量,这样通过一些多项式的变化来使得它不计入复杂度,而一些范围较小的信息是可以枚举的。

可以归纳证明\(g(i,j)\) 一定是形如 \(a_0+a_12^{-j}+a_22^{-2j}....+a_k2^{-kj}\) 的形式(但是 \(j\) 必须在合法范围内),说得更明确一点,这个 \(a\) 就是只和 \(i\) 有关的系数,如果我们想要 \(g(i,j)\) 就把 \(j\) 带进这个柿子就行了。

这一步我还不是特别理解,因为归纳法只能证明却不能告诉我们为什么有这个结论。主要还是领会多项式的思想,以后可能会补充为什么。

下面来证明给出的结论,设 \(b\)\(g(l)\)\(g(r)\) 的各项系数的卷积,因为 \(g\) 相乘可以看成系数卷积,设 \(s_i\)\(g(i,j)\) 的合法范围,下面的 \(t\) 表示卷积后的项数:

\[\begin{align}g(i,j)&=\sum_{k=j}^{s_i}2^kf(i,k)\\&=\sum_{k=j}^{s_i}2^k\cdot2^{-2k-2}g(l,k+1)g(r,k+1)\\&=\sum_{k=j}^{s_i}2^{-k-2}\sum_{l=0}^{t}b_l2^{-l(k+1)}\\&=\frac{1}{2}\sum_{l=0}^{t}b_l\cdot2^{-l-1}\sum_{k=j}^{s_i}2^{-(l+1)k}\\&=\frac{1}{2}\sum_{l=0}^tb_l\cdot 2^{-l-1}\frac{2^{-(s_i+1)(l+1)}-2^{-j(l+1)}}{2^{-l-1}-1}\end{align} \]

多项式的变量就是 \(j\),和 \(j\) 无关的可以放到常数那里,所以卷积完的结果有 \(2^{-j(l+1)}\),那么就归到多项式系数的第 \(l+1\) 项即可。现在我们的复杂度就和深度没关系了,每次暴力卷积算 \(b\) 即可,由于每两个点只会在 \(lca\) 处有 \(1\) 的贡献(又来了),所以时间复杂度 \(O(n^2)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 5005;
const int MOD = 1e9+7;
#define int long long
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,inv,ans,ls[M],rs[M],h[M],a[M][M],b[M],siz[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
void dfs(int u)
{
	if(!ls[u])
	{
		siz[u]=1;
		a[u][0]=qkpow(2,h[u]);
		return ;
	}
	dfs(ls[u]);
	dfs(rs[u]);
	h[u]=min(h[ls[u]],h[rs[u]])-1;//算j的范围
	if(h[u]<0) return ;
	siz[u]=siz[ls[u]]+siz[rs[u]];
	memset(b,0,sizeof b);
	for(int i=0;i<siz[ls[u]];i++)//暴力卷积
		for(int j=0;j<siz[rs[u]];j++)
			b[i+j]=(b[i+j]+a[ls[u]][i]*a[rs[u]][j])%MOD;
	for(int i=0;i<siz[u];i++)
	{
		int x=qkpow(2,(MOD-1)-i-1);
		int fk=qkpow(x-1,MOD-2);//分母
		b[i]=b[i]*qkpow(inv,i+1)%MOD;
		a[u][i+1]-=fk%MOD*b[i]%MOD;
		a[u][0]=(a[u][0]+qkpow(x,h[u]+1)*fk%MOD*b[i])%MOD;
	}
	for(int i=0;i<siz[u];i++)
		a[u][i]=a[u][i]*inv%MOD;
}
signed main()
{
	n=read();inv=(MOD+1)/2;
	for(int i=1;i<=n;i++)
	{
		int op=read();
		if(op==0) ls[i]=read(),rs[i]=read();
		else h[i]=read();
	}
	dfs(1);
	for(int i=0;i<siz[1];i++)
		ans=(ans+a[1][i])%MOD;
	printf("%lld\n",(ans+MOD)%MOD);
}

后缀数组

不好意思又咕了。

posted @ 2021-02-27 19:04  C202044zxy  阅读(124)  评论(0编辑  收藏  举报