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的边,求有多少边满足条件? 思路: 首先,这道题也可 ...
随机推荐
- 半天撸一个简易版mybatis
为什么需要持久层框架? 首先我们先看看使用原生jdbc存在的问题? public static void main(String[] args) { Connection connection = n ...
- vue3.x全局$toast、$message、$loading等js插件
有时候我们需要使用一些类似toast,messge.loading这些跟js交互很频繁的插件,vue3.x这类插件的定义跟vue2.x插件稍大,而且相对变得复杂了一点点. 第一种.需要时创建,用完移除 ...
- Java:包装类小记
Java:包装类 对 Java 中的 包装类 这个概念,做一个微不足道的小小小小记 基本数据&包装类 四类八种基本数据类型: 数据类型 关键字 内存占用 取值范围 字节型 byte 1个字节 ...
- Java-基础-LinkedList
1. 简介 LinkedList 同时实现了List和Deque接口,也就是说它既可以看作是一个顺序容器,又可以看作是双向队列. 既然是双向列表,那么它的每个数据节点都一定有两个指针,分别指向它的前驱 ...
- Huffman算法
一.Huffman算法介绍 霍夫曼编码(英语:Huffman Coding),又译为哈夫曼编码.赫夫曼编码,是一种用于无损数据压缩的熵编码(权编码)算法.在计算机数据处理中,霍夫曼编码使用变长编码表对 ...
- python文件读写及修改
转载:https://www.cnblogs.com/zhxwind/p/8761618.html 文件的读写有三种形式:读.写和追加. 一.读模式 r 和读写模式 r+ 1.读模式 r 读模式r特点 ...
- 最短路spaf及dijkstra模板
spaf的双端队列优化: #include<bits/stdc++.h> #define ll long long const ll maxn=210000; using namespac ...
- Tarjan算法 求 有向图的强连通分量
百度百科 https://baike.baidu.com/item/tarjan%E7%AE%97%E6%B3%95/10687825?fr=aladdin 参考博文 http://blog.csdn ...
- coreseek使用心得
基本使用方法: D:\coreseek-4.1\bin\searchd -c D:\coreseek-4.1\etc\article.conf --stop 停止服务 D:\coreseek-4.1\ ...
- 设计模式学习-使用go实现建造者模式
建造者模式 定义 适用范围 与工厂模式的区别 优点 缺点 参考 建造者模式 定义 Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式. 建造者模式(Builder Patt ...