[正睿集训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\) 深度上,并考虑其子树的放置方案数,转移就枚举左右儿子放置的深度:
设 \(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\) 表示卷积后的项数:
多项式的变量就是 \(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);
}
后缀数组
不好意思又咕了。