Codeforces 题面传送门 & 洛谷题面传送门

一种换根 dp 的做法。

首先碰到这类题目,我们很明显不能真的把图 \(G\) 建出来,因此我们需要观察一下图 \(G\) 有哪些性质。很明显我们可以把它看作 \(T_1,T_2\) 上分别有一个标记,每次可以将 \(T_1,T_2\) 上的某个标记沿着某条边移动一步,问 \(k\) 步之后标记刚好回到原位的方案数是多少。

显然两棵树是独立的,因此可以分开来考虑,我们记 \(dp1_{i,j}\) 表示 \(T_1\) 中从 \(i\) 开始走 \(j\) 步又回到 \(i\) 的方案数,\(dp2_{i,j}\) 也同理,那么显然有 \(ans=\sum\limits_{x\in T_1}\sum\limits_{y\in T_2}\sum\limits_{i=0}^kdp1_{x,i}dp2_{y,k-i}\dbinom{k}{i}\),把组合数裂开可以得到 \(ans=k!\sum\limits_{i=0}^k\sum\limits_{x\in T_1}\dfrac{dp_{x,i}}{i!}\sum\limits_{y\in T_2}\dfrac{dp_{y,k-i}}{(k-i)!}\),两部分分别求个和然后 \(\mathcal O(k)\) 合并即可。

因此接下来我们的任务就是求出 \(dp1_{i,j}\) 和 \(dp2_{i,j}\),两部分是等价的,因此考虑怎样求 \(dp1_{i,j}\),在下文中为了方便起见,我们用 \(dp_{i,j}\) 代替 \(dp1_{i,j}\),我们不妨以 \(1\) 为根做一遍 DFS,设 \(f_{x,j}\) 表示从 \(x\) 开始,只经过 \(x\) 子树内,\(j\) 步后回到 \(i\) 的方案数,那么我们显然可以枚举 \(i\) 第一步到达的点是什么,经过多少步之后第一次回到 \(x\),那么有 \(f_{x,j}=\sum\limits_{y\in\text{son}(x)}\sum\limits_{t=2k,t\le j,k\in\mathbb{Z}^+}f_{y,t-2}f_{x,j-t}\),这个显然可以一遍 DFS 带走,我们再设 \(out_{x,j}\) 表示从 \(fa_x\) 开始,不经过 \(x\) 子树内的点,\(j\) 步回到 \(x\) 的方案数,那么我们还是可以枚举第一步到达的点 \(y\) 以及第一次回到 \(fa_x\) 的时间 \(t\),那么这种情况对 \(out_{x,j}\) 的贡献就是 \(v_t\times out_{x,t}\),其中如果 \(y\) 是 \(fa_x\) 的父亲,那么 \(v_t=out_{fa_x,t}\),否则 \(v_t=f_{y,t}\),这个暴力搞是 \(\mathcal O(deg_i^2)\) 的,碰到菊花图就凉凉了,考虑优化,注意到这东西可以写成 \(\sum\limits_{t}S_t\times out_{x,t}\) 的形式,因此我们可以先提前一遍预处理求出所有子树的 \(f_{y,t}\) 之和,扫到一个子树时就把该子树内的贡献撤销掉,回溯时再加上,这样复杂度就 ok 了。最后 \(dp_{x,j}\) 的转移方程就比较容易了,还是枚举第一步到达的点 \(y\) 和第一次回到 \(x\) 的时间 \(t\),那么如果 \(y\) 是 \(x\) 的父亲贡献就是 \(out_{x,t-2}dp_{x,j-t}\),否则是 \(f_{y,t-2}dp_{x,j-t}\)。

时间复杂度 \(\mathcal O(nk^2)\)。

可以写个类封装一下,避免写过多一模一样的代码。

const int MAXN=4000;
const int MAXK=75;
const int MOD=998244353;
int k,fac[MAXK+5],ifac[MAXK+5];
void init_fac(int n){
for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
struct tree_solver{
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0,sum[MAXK+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
void read(){for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);}
int f[MAXN+5][MAXK+5],dp[MAXN+5][MAXK+5],out[MAXN+5][MAXK+5],dep[MAXN+5];
void dfs0(int x,int fa){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa) continue;
dep[y]=dep[x]+1;dfs0(y,x);
}
}
void dfs1(int x,int fa,int t){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa) continue;dfs1(y,x,t);
for(int i=0;i<t;i+=2) f[x][t]=(f[x][t]+1ll*f[x][i]*f[y][t-i-2])%MOD;
}
}
void dfs2(int x,int fa,int t){
vector<int> s(t+1);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];
if(y==fa) for(int i=0;i<t;i+=2) s[i]=(s[i]+out[x][t-i-2])%MOD;
else for(int i=0;i<t;i+=2) s[i]=(s[i]+f[y][t-i-2])%MOD;
}
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa) continue;
for(int i=0;i<t;i+=2) s[i]=(s[i]-f[y][t-i-2]+MOD)%MOD;
for(int i=0;i<t;i+=2) out[y][t]=(out[y][t]+1ll*s[i]*out[y][i])%MOD;
for(int i=0;i<t;i+=2) s[i]=(s[i]+f[y][t-i-2])%MOD;
dfs2(y,x,t);
}
}
void solve(){
dfs0(1,0);
for(int i=1;i<=n;i++) dp[i][0]=f[i][0]=out[i][0]=1;
for(int i=2;i<=k;i+=2){
dfs1(1,0,i);dfs2(1,0,i);
// for(int j=1;j<=n;j++) printf("%d %d %d %d\n",j,i,f[j][i],out[j][i]);
}
for(int i=2;i<=k;i+=2) for(int x=1;x<=n;x++){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];
if(dep[y]>dep[x]){
for(int j=0;j<i;j+=2) dp[x][i]=(dp[x][i]+1ll*dp[x][i-j-2]*f[y][j])%MOD;
} else {
for(int j=0;j<i;j+=2) dp[x][i]=(dp[x][i]+1ll*dp[x][i-j-2]*out[x][j])%MOD;
}
} //printf("%d %d %d\n",x,i,dp[x][i]);
}
for(int i=0;i<=k;i+=2) for(int x=1;x<=n;x++)
sum[i]=(sum[i]+1ll*ifac[i]*dp[x][i])%MOD;
// for(int i=0;i<=k;i+=2) printf("%d\n",sum[i]);
}
} t1,t2;
int main(){
scanf("%d%d%d",&t1.n,&t2.n,&k);if(k&1) return puts("0"),0;
t1.read();t2.read();init_fac(k);t1.solve();t2.solve();
int ans=0;for(int i=0;i<=k;i+=2) ans=(ans+1ll*t1.sum[i]*t2.sum[k-i])%MOD;
printf("%d\n",1ll*ans*fac[k]%MOD);
return 0;
}

Codeforces 997D - Cycles in product(换根 dp)的更多相关文章

  1. Codeforces 891D - Sloth(换根 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 换根 dp 好题. 为啥没人做/yiw 首先 \(n\) 为奇数时答案显然为 \(0\),证明显然.接下来我们着重探讨 \(n\) 是偶数 ...

  2. [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]

    题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...

  3. 2018.10.15 NOIP训练 水流成河(换根dp)

    传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...

  4. 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市

    P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...

  5. 小奇的仓库:换根dp

    一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...

  6. 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)

    题意 ​ 题目链接:https://www.luogu.org/problem/P4827 ​ 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...

  7. Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)

    题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...

  8. bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp

    题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...

  9. codeforces1156D 0-1-Tree 换根dp

    题目传送门 题意: 给定一棵n个点的边权为0或1的树,一条合法的路径(x,y)(x≠y)满足,从x走到y,一旦经过边权为1的边,就不能再经过边权为0的边,求有多少边满足条件? 思路: 首先,这道题也可 ...

随机推荐

  1. 手把手教你学Dapr - 1. .Net开发者的大时代

    Dapr全称 Distributed Application Runtime,分布式应用运行时 Dapr的口号 简化云原生应用开发,聚焦在应用的核心逻辑,让代码简单.可移植 Dapr的目标 最佳实践的 ...

  2. RabbitMQ设计原理解析

    背景 RabbitMQ现在用的也比较多,但是没有过去那么多啦.现在很多的流行或者常用技术或者思路都是从过去的思路中演变而来的.了解一些过去的技术,对有些人来说可能会产生众里寻他千百度的顿悟,加深对技术 ...

  3. the Agiles Scrum Meeting 6

    会议时间:2020.4.14 20:00 1.每个人的工作 今天已完成的工作 增量组:开发广播正文展开收起功能 issues:增量组:广播正文展开收起功能实现 完善组:修复冲刺部分的bug issue ...

  4. Noip模拟68 2021.10.4

    T1 玩水 成功在考试的时候注释掉正解,换成了暴力,只因为不敢保证正解思路的正确 脑子瓦特了,不知道把暴力打成函数拼在一起,不知道当时咋想的.... 就是你找有没有一个点上面和左面的字符一样, 如果这 ...

  5. 计算机网络之介质访问控制(静态划分信道、FDM、TDM、STDM、WDM、CDM)、(动态划分信道、ALOHA、CSMA、CSMA/CD、CSMA/CA)、令牌传递协议

    文章转自:https://blog.csdn.net/weixin_43914604/article/details/104935912 学习课程:<2019王道考研计算机网络> 学习目的 ...

  6. ASP.NET Core 学习笔记 第四篇 ASP.NET Core 中的配置

    前言 说道配置文件,基本大多数软件为了扩展性.灵活性都会涉及到配置文件,比如之前常见的app.config和web.config.然后再说.NET Core,很多都发生了变化.总体的来说技术在进步,新 ...

  7. 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析

    SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...

  8. std::string类详解

    之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必 担心内存是否足够.字符串长度等等,而且作为一个类出现,他集成的操作函数足以完成我们大多数情况下(甚至 ...

  9. hdu 5092 Seam Carving (简单数塔DP,题没读懂,,不过可以分析样例)

    题意: 给一个m*n的矩阵,每格上有一个数. 找从第1行到第m行的一条路径,使得这条路径上的数之和最小. 路径必须满足相邻两行所选的两个数的纵坐标相邻(即一个格子必须是另一个格子的周围八个格子中的一个 ...

  10. 二.什么是Promise

    二.什么是Promise 1.理解 2.promise 的状态改变 3.promise的基本流程 4.promise的基本使用 1.理解 抽象表达: Promise 是JS 中进行异步编程的新的解决方 ...