Codeforces 997D - Cycles in product(换根 dp)
一种换根 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)的更多相关文章
- Codeforces 891D - Sloth(换根 dp)
Codeforces 题面传送门 & 洛谷题面传送门 换根 dp 好题. 为啥没人做/yiw 首先 \(n\) 为奇数时答案显然为 \(0\),证明显然.接下来我们着重探讨 \(n\) 是偶数 ...
- [BZOJ4379][POI2015]Modernizacja autostrady[树的直径+换根dp]
题意 给定一棵 \(n\) 个节点的树,可以断掉一条边再连接任意两个点,询问新构成的树的直径的最小和最大值. \(n\leq 5\times 10^5\) . 分析 记断掉一条边之后两棵树的直径为 \ ...
- 2018.10.15 NOIP训练 水流成河(换根dp)
传送门 换根dp入门题. 貌似李煜东的书上讲过? 不记得了. 先推出以1为根时的答案. 然后考虑向儿子转移. 我们记f[p]f[p]f[p]表示原树中以ppp为根的子树的答案. g[p]g[p]g[p ...
- 换根DP+树的直径【洛谷P3761】 [TJOI2017]城市
P3761 [TJOI2017]城市 题目描述 从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作.这个地区一共有ri座城市,<-1条高速公路,保证了任意两运城市之间都可以通过高速公 ...
- 小奇的仓库:换根dp
一道很好的换根dp题.考场上现场yy十分愉快 给定树,求每个点的到其它所有点的距离异或上m之后的值,n=100000,m<=16 只能线性复杂度求解,m又小得奇怪.或者带一个log像kx一样打一 ...
- 国家集训队 Crash 的文明世界(第二类斯特林数+换根dp)
题意 题目链接:https://www.luogu.org/problem/P4827 给定一棵 \(n\) 个节点的树和一个常数 \(k\) ,对于树上的每一个节点 \(i\) ,求出 \( ...
- Acesrc and Travel(2019年杭电多校第八场06+HDU6662+换根dp)
题目链接 传送门 题意 两个绝顶聪明的人在树上玩博弈,规则是轮流选择下一个要到达的点,每达到一个点时,先手和后手分别获得\(a_i,b_i\)(到达这个点时两个人都会获得)的权值,已经经过的点无法再次 ...
- bzoj 3566: [SHOI2014]概率充电器 数学期望+换根dp
题意:给定一颗树,树上每个点通电概率为 $q[i]$%,每条边通电的概率为 $p[i]$%,求期望充入电的点的个数. 期望在任何时候都具有线性性,所以可以分别求每个点通电的概率(这种情况下期望=概率 ...
- codeforces1156D 0-1-Tree 换根dp
题目传送门 题意: 给定一棵n个点的边权为0或1的树,一条合法的路径(x,y)(x≠y)满足,从x走到y,一旦经过边权为1的边,就不能再经过边权为0的边,求有多少边满足条件? 思路: 首先,这道题也可 ...
随机推荐
- 这部分布式事务开山之作,凭啥第一天预售就拿下当当新书榜No.1?
大家好,我是冰河~~ 今天,咱们就暂时不聊[精通高并发系列]了,今天插播一下分布式事务,为啥?因为冰河联合猫大人共同创作的分布式事务领域的开山之作--<深入理解分布式事务:原理与实战>一书 ...
- Vue3学习(八)之 Vue CLI多环境配置
一.前言 这里相对于之前就没那么麻烦了,通俗点说就是使用配置文件来管理多环境,实现环境的切换. 二.实现切换 1.增加开发和生产配置文件 在web的根目录下,创建开发环境切换配置文件.env.dev, ...
- 【c++ Prime 学习笔记】第19章 特殊工具与技术
某些程序对内存分配有特殊要求,不能直接使用标准内存管理机制 重载new和delete算符可控制内存分配的过程 19.1.1 重载new和delete 说法"重载new和delete" ...
- 验证域用户(C#)
代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Intero ...
- Scrum Meeting 1补充会议
日期:2021年04月24日 会议主要内容概述: 本次会议于11:30举行,对项目架构做出了重要调整,并根据该调整修改了第1次例会报告中后两日计划完成的工作部分. 一.架构调整 会上讨论了用户模块相关 ...
- 第一次Scrum Metting
日期: 2021年4月23日 会议主要内容: 会议主要各自介绍一下所做任务,讨论了前后端接口定义以及服务器购买和接下来任务分配. 一.进度情况 组员 负责 两日已完成的工作 后两日计划完成的工作 工作 ...
- BUAA_2020_软件工程_个人项目作业
作业抬头(1') 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 个人项目作业 我在这个课程的目标是 了解软件工程的技术,掌握工程化开发的能力 这 ...
- oo第一次博客-三次表达式求导的总结与反思
一.问题回顾与基本设计思路 三次作业依次是多项式表达式求导,多项式.三角函数混合求导,基于三角函数和多项式的嵌套表达式求导. 第一次作业想法很简单,根据指导书,我们可以发现表达式是由各个项与项之间的运 ...
- hystrix的dashboard和turbine监控
当我们的应用程序使用了hystrix后,每个具体的hystrixCommand命令执行后都会产生一堆的监控数据,比如:成功数,失败数,超时数以及与之关联的线程池信息等.既然有了这些监控数据数据,那么我 ...
- 镜头Lens Image circle像圈的解释是什么意思
Image circle镜头中指的是:像圈 像圈(image circle)是指入射光线通过镜头后,在焦平面上呈现出的圆形的明亮清晰的影像幅面,也称像面大小.镜头像圈由镜头光学结构决定,一旦设计完成, ...