【动态规划】【树形dp】CF1868C Travel Plan
题目描述
给定一颗
求所有树(
算法概述
对于这种全局多情况统计问题,考虑用期望转化,我们发现,一条路径的期望最大值贡献只和这条路径的长度有关,我们枚举最大值
最后算出来一条路径的贡献就是:
此处
考虑
这样
考虑转移
我们就得到了满二叉树的答案。
由于我们知道完全二叉树可以被拆成若干个满二叉树,我们递归进行以下过程,我们检查当前点的数量判断最底层从左往右是否超过了中点,如果超过,左边就是一个深度为
预处理状态数
统计答案时有快速幂,复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const int MOD = 998244353,N = 205,M = 1e5 + 5;
typedef long long ll;
ll g[N][N],f[N][N],n,m,pw2[N],dp1[N][N],dp2[N][N],E[N];
inline ll ksm(ll base,ll pts)
{
if(pts < 0) return 0;
ll ret = 1;
for(;pts > 0;pts >>= 1,base = base * base % MOD)
if(pts & 1)
ret = ret * base % MOD;
return ret;
}
inline void init()//一个点深度为 1
{
g[1][0] = 1;
for(int i = 2;i <= 128;i++)
{
g[i][0] = 1;
for(int j = 1;j <= 128;j++)
g[i][j] = 2ll * g[i - 1][j - 1] % MOD;
}
f[1][0] = 1;
for(int i = 2;i <= 128;i++)
{
f[i][0] = (ksm(2,i) - 1 + MOD) % MOD;
for(int j = 1;j <= 128;j++)
{
f[i][j] = (g[i][j] + 2ll * f[i - 1][j] % MOD) % MOD;
for(int k = 0;k <= j - 2;k++)
f[i][j] = (f[i][j] + g[i - 1][k] * g[i - 1][j - k - 2] % MOD) % MOD;
}
}
pw2[0] = 1;
for(int i = 1;i <= 62;i++) pw2[i] = pw2[i - 1] * 2ll;
}
inline void dfs(int dep,ll x)
{
if(!x) return;
ll mid = pw2[dep - 1] - 1 + pw2[dep - 2];
int rt;
if(x <= mid) rt = dep - 2,dfs(dep - 1,x - pw2[dep - 2]);
else rt = dep - 1,dfs(dep - 1,x - pw2[dep - 1]);
dp1[dep][0] = (1 + dp1[dep - 1][0] + f[rt][0]) % MOD;
dp2[dep][0] = 1;
for(int i = 1;i <= 128;i++)
dp2[dep][i] = (g[rt][i - 1] + dp2[dep - 1][i - 1]) % MOD;
for(int i = 1;i <= 128;i++)
{
dp1[dep][i] = (dp2[dep][i] + dp1[dep - 1][i] + f[rt][i]) % MOD;
for(int k = 0;k <= i - 2;k++) dp1[dep][i] = (dp1[dep][i] + g[rt][k] * dp2[dep - 1][i - 2 - k] % MOD) % MOD;
}
}
ll pwj[M];
int prime[M],tot = 0,vis[M];
inline void oper(int x,int y)
{
tot = 0;
fill(vis + 1,vis + y + 1,0); pwj[1] = 1;
for(int i = 2;i <= y;i++)
{
if(!vis[i]){prime[++tot] = i;pwj[i] = ksm(i,x);}
for(int j = 1;j <= tot && 1ll * prime[j] * i <= y;j++)
{
vis[i * prime[j]] = 1;
pwj[i * prime[j]] = pwj[i] * pwj[prime[j]] % MOD;
if(!(i % prime[j])) break;
}
}
}
int main()
{
// freopen("c.txt","r",stdin);
init();
int T;
cin>>T;
while(T--)
{
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
memset(E,0,sizeof(E));
cin>>n>>m;
int dep = 1;
while(pw2[dep] - 1 < n) ++dep;
dfs(dep,n);
// cout<<dp1[dep][0]<<" "<<dp1[dep][1]<<" "<<dp1[dep][2]<<endl;
for(int i = 1;i <= 128;i++) //点数
{
oper(i,m);
for(int j = 1;j <= m;j++)
E[i] = (E[i] + j * (pwj[j] - pwj[j - 1] + MOD) % MOD) % MOD;
E[i] = E[i] * ksm(m,n - i) % MOD;
}
ll ans = 0;
for(int i = 0;i <= 127;i++) ans = (ans + dp1[dep][i] * E[i + 1] % MOD) % MOD;
cout<<ans<<endl;
}
return 0;
}
虽然这道题最终没有用期望,但是用期望计算此类全局计数问题通常是一种好方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话