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的边,求有多少边满足条件? 思路: 首先,这道题也可 ...
随机推荐
- .Net Core中使用ElasticSearch(一)
一.安装配置 在官网下载Es,注意版本号,不同大版本号之间差异很大.我安装的是7.14.0版本 1.1 安装成服务 cmd 进入bin目录下执行 elasticsearch-service.bat i ...
- vue3.x移动端页面基于vue-router的路由切换动画
移动端页面切换一般都具有动画,我们既然要做混合开发,做完之后还是不能看起来就像一个网页,所以我们基于vue-router扩展了一个页面切换push和pop的动画.这是一篇比较硬核的帖子,作者花了不少精 ...
- STM32单片机的学习方法(方法大体适用所有开发版入门)
1,一款实用的开发板. 这个是实验的基础,有时候软件仿真通过了,在板上并不一定能跑起来,而且有个开发板在手,什么东西都可以直观的看到,效果不是仿真能比的.但开发板不宜多,多了的话连自己都不知道该学哪个 ...
- 洛谷 P3147 [USACO16OPEN]262144 P
链接: P3147 P3146双倍经验 前言: 今天发现的一道很有意思的DP题 分析: 第一眼以为是区间DP,于是设f[i][j]为从第i个数到第j个数可以合出的最大值,但思考后发现并不能简单合并,并 ...
- 使用spire.doc导出支持编辑Latex公式的标准格式word
背景 之前有的教辅标注需求,在导出题库的时候希望顺便导出可以查看word,方便线下预览成品效果,因为只是用来预览并且为了沿用前端的样式,当时方案就是直接生成html,写个word的文件头,这样就可以用 ...
- ELK 脚本自动化删除索引
kibana有自带接口,可通过自带的API接口 通过传参来达到删除索引的目的. # 删除15天前的索引 curl -XDELETE "http://10.228.81.161:9201/pa ...
- Linux的inode与block
1,inode包含文件的元信息,具体来说有以下内容: 文件的字节数 文件拥有者的User ID 文件的Group ID 文件的读.写.执行权限 文件的时间戳,共有三个:ctime指inode上次文件属 ...
- 区块链开发学习第三章:私有链上部署helloBlockchain简单合约
前面讲了部署私有链以及新增账户,现在进行到了部署合约了,此操作真是踩了无数无数无数的坑,到写文章为止确实是已经部署好了,但是还有些坑是还没有解决的! 一.Solidity编译器 开始的时候用的http ...
- NAT & 防火墙
NAT 网络地址转换 NAT产生背景 今天,无数快乐的互联网用户在尽情享受Internet带来的乐趣.他们浏览新闻,搜索资料,下载软件,广交新朋,分享信息,甚至于足不出户获取一切日用所需.企业利用互联 ...
- 设置IDEA启动,不要自动打开上次使用时的项目
打开idea时自动加载最近编辑的项目,很费时间,关闭设置如下