计算器
咕咕咕
二叉树
题目描述
有一棵无穷大的完全二叉树 (T),任意一个点都有两个子节点。给定一棵有限的二叉树 (G),满足除叶节点外每个节点都有恰好两个儿子,你需要将 (G) 的每个节点分别对应到 (T) 的节点上面去,使得对于 (G) 的叶节点深度为 (h[i]),对于 (G) 的非叶节点的两个子节点 (ls,rs),需要分别在 (T) 的对应节点的左右子树上。
求对应的方案数,对 (1e9+7) 取模。
(nleq 5000,h_ileq 10^9)
解法
我们考虑 (G),可以在树上 (dp),设 (f[i][j]) 表示点 (i) 被放在 (T) 的 (j) 深度上,并考虑其子树的放置方案数,转移就枚举左右儿子放置的深度:
设 (g(i,j)=sum_{kgeq 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) 表示卷积后的项数:
多项式的变量就是 (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
",(ans+MOD)%MOD);
}
后缀数组
不好意思又咕了。