洛谷P5206 数树
题意:
task0,给定两棵树T1,T2,取它们公共边(两端点相同)加入一张新的图,记新图连通块个数为x,求yx。
task1,给定T1,求所有T2的task0之和。
task2,求所有T1的task1之和。
解:y = 1的时候特殊处理,就是总方案数。
task0,显然按照题意模拟即可。
task1,对某个T2,设有k条边相同,那么连通块数就是n - k。要求的就是

对于每个T2,前面yn都是一样的,所以直接去掉,最后乘上即可。关注后面这个东西怎么求。令y' = 1/y,E是公共边集。
注意到

这里下式是枚举边集E的子集S,对每个的子集贡献求和。
注意上式先枚举a再求组合数,相当于枚举在边集里选a条边,然后枚举选哪a条边。也就是枚举子集。
也就是下面这段话想表达的。摘自Cyhlnj。里面还提到了一个n3的矩阵树定理做法,神奇。

容斥写法在下不会T_T
下一步,把S提前枚举,在不同的E中同一个S的贡献总是相同的。考虑一个S会对哪些E产生贡献,也就是它的贡献会被计算多少次。
这|S|条边会形成若干个连通块。这些连通块通过加上一些边可以形成树。这些新边没有任何限制,于是就是连通块的生成树计数。
这里又有若干种推法......个人认为最简单的是利用prufer序列求解。

摘自Joker_69。
令z = y' - 1,m = 边集为S时的连通块数 = n - |S|,第i号连通块有ai个点,于是我们的答案变成了这样:

这个东西怎么求呢?注意到在T1中选择任意的边集S等价于把T1划分为若干个连通块,用这些边连起来。于是就考虑树形DP。
这后面这个求积,要乘上每个连通块的大小,有个暴力是f[x][i]表示x为根,x所在连通块大小为i的所有方案权值和。
n2过不了,于是换个思路就是在每个连通块中选一个关键点的方案数。
因为是以联通块为依据DP,所以变形一下,加上之前忽略的yn,我们有:

于是状态设计有f[x][0/1]表示在以x为根的子树中,x所在连通块是否选择了关键点的所有方案权值之和。
每个联通块的贡献是z-1n,且我们只在关键点被选出来的那一瞬间计算这个联通块的贡献。
同时由于每个连通块的贡献要乘起来,那么所有方案之和还是要乘起来,等价于每个方案两两求积再相加。
口胡了半天还是写一下方程吧。
f[x][] = ;
f[x][] = invz * n % MO; LL t0 = f[x][] * f[y][] % MO + f[x][] * f[y][] % MO;
LL t1 = f[x][] * f[y][] % MO + f[x][] * f[y][] % MO + f[x][] * f[y][] % MO;
f[x][] = t0 % MO;
f[x][] = t1 % MO;
task2:问题变得严重起来......
跟task1一样,对于某个T1和T2的组合,它的贡献仍能拆成它的子集的贡献。
设g(E)为给定E这个边集之后的生成树个数,由task1可得g(E) = nm-2∏ai。
枚举E为一定相同的边集,剩下的边随便连。那么对于T1的g(E)种情况,T2都有g(E)种情况。
所以E这个边集的贡献为z|E|g2(E)。
m还是连通块数,我们暴力展开g(E),并把与m有关的项放到∏里面,无关的提到外面,令r = n2/z,那么答案就是:

接下来这一步很毒瘤...我们考虑这个式子有什么实际意义。
前面的边集把这个图分成了若干个森林。每个连通块一定是树。后面相当于给每个连通块赋了ai2r的权值,并把权值乘起来作为这个边集的贡献。
设fi为大小为i的树的贡献,对应的EGF是F(x),gi为大小为i的图的贡献,对应的EGF是G(x)
那么有这样一个式子:G(x) = eF(x)
考虑fi是多少:每种树的权值都是i2r,一共有ii-2种树,贡献加起来是iir。
这样就对F(x)做exp,然后拿G(x)的第n项出来搞一搞就是答案了。
多项式操作别写错了......我一开始WA了20分是因为有这样的一句话:n * n
然后两个n乘起来爆int了......这题神奇的一批...
#include <cstdio>
#include <algorithm>
#include <cstring> typedef long long LL;
const int N = ;
const LL MO = ; inline LL qpow(LL a, LL b) {
LL ans = ;
a %= MO;
while(b) {
if(b & ) ans = ans * a % MO;
a = a * a % MO;
b = b >> ;
}
return ans;
} struct Edge {
int nex, v;
}edge[N << ]; int tp; int n, e[N];
LL Y, z; inline void add(int x, int y) {
tp++;
edge[tp].v = y;
edge[tp].nex = e[x];
e[x] = tp;
return;
} namespace t0 {
int fa[N];
void DFS(int x, int f) {
fa[x] = f;
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
if(y == f) continue;
DFS(y, x);
}
return;
}
inline void solve() {
if(Y == ) {
puts("");
return;
}
for(int i = , x, y; i < n; i++) {
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
DFS(, );
int k = ;
for(int i = , x, y; i < n; i++) {
scanf("%d%d", &x, &y);
if(fa[x] == y || fa[y] == x) {
k++;
}
}
LL ans = qpow(Y, n - k);
printf("%lld\n", ans);
return;
}
} namespace t1 {
LL f[N][], invz;
void DFS(int x, int father) {
f[x][] = ;
f[x][] = invz * n % MO;
//printf("x = %d fa = %d \n", x, father);
for(int i = e[x]; i; i = edge[i].nex) {
int y = edge[i].v;
//printf("y = %d \n", y);
if(y == father) continue;
DFS(y, x);
LL t0 = f[x][] * f[y][] % MO + f[x][] * f[y][] % MO;
LL t1 = f[x][] * f[y][] % MO + f[x][] * f[y][] % MO + f[x][] * f[y][] % MO;
f[x][] = t0 % MO;
f[x][] = t1 % MO;
}
//printf("X = %d f[x][0] = %lld f[x][1] = %lld \n", x, f[x][0], f[x][1]);
return;
}
inline void solve() {
if(Y == ) {
LL ans = qpow(n, n - );
printf("%lld\n", ans);
return;
}
z = qpow(Y, MO - ); z = (z - + MO) % MO;
invz = qpow(z, MO - );
for(int i = , x, y; i < n; i++) {
scanf("%d%d", &x, &y);
add(x, y); add(y, x);
}
DFS(, );
LL ans = f[][] * qpow(n, MO - ) % MO * qpow(z, n) % MO * qpow(Y, n) % MO;
printf("%lld\n", ans);
return;
}
} namespace t2 { typedef LL arr[N * ];
const LL G = ; int r[N * ];
arr A, B, a, b, inv_t, exp_t, ln_t, ln_t2;
LL pw[N]; inline void prework(int n) {
static int R = ;
if(R == n) return;
R = n;
int lm = ;
while(( << lm) < n) lm++;
for(int i = ; i < n; i++) {
r[i] = (r[i >> ] >> ) | ((i & ) << (lm - ));
}
return;
} inline void NTT(LL *a, int n, int f) {
prework(n);
for(int i = ; i < n; i++) {
if(i < r[i]) std::swap(a[i], a[r[i]]);
}
for(int len = ; len < n; len <<= ) {
LL Wn = qpow(G, (MO - ) / (len << ));
if(f == -) Wn = qpow(Wn, MO - );
for(int i = ; i < n; i += (len << )) {
LL w = ;
for(int j = ; j < len; j++) {
LL t = a[i + len + j] * w % MO;
a[i + len + j] = (a[i + j] - t) % MO;
a[i + j] = (a[i + j] + t) % MO;
w = w * Wn % MO;
}
}
}
if(f == -) {
LL inv = qpow(n, MO - );
for(int i = ; i < n; i++) {
a[i] = a[i] * inv % MO;
}
}
return;
} void Inv(const LL *a, LL *b, int n) {
if(n == ) {
b[] = qpow(a[], MO - );
b[] = ;
return;
}
Inv(a, b, n >> );
/// ans = b[i] * (2 - a[i] * b[i])
memcpy(A, a, n * sizeof(LL)); memset(A + n, , n * sizeof(LL));
memcpy(B, b, n * sizeof(LL)); memset(B + n, , n * sizeof(LL));
NTT(A, n << , ); NTT(B, n << , );
for(int i = ; i < (n << ); i++) b[i] = B[i] * ( - A[i] * B[i] % MO) % MO;
NTT(b, n << , -);
memset(b + n, , n * sizeof(LL));
return;
} inline void getInv(const LL *a, LL *b, int n) {
int len = ;
while(len < n) len <<= ;
memcpy(inv_t, a, n * sizeof(LL)); memset(inv_t + n, , (len - n) * sizeof(LL));
Inv(inv_t, b, len);
memset(b + n, , (len - n) * sizeof(LL));
return;
} inline void der(const LL *a, LL *b, int n) {
for(int i = ; i < n - ; i++) {
b[i] = a[i + ] * (i + ) % MO;
}
b[n - ] = ;
return;
} inline void ter(const LL *a, LL *b, int n) {
for(int i = n - ; i >= ; i--) {
b[i] = a[i - ] * qpow(i, MO - ) % MO;
}
b[] = ;
return;
} inline void getLn(const LL *a, LL *b, int n) {
getInv(a, ln_t, n);
der(a, ln_t2, n);
int len = ;
while(len < * n) len <<= ;
memset(ln_t + n, , (len - n) * sizeof(LL));
memset(ln_t2 + n, , (len - n) * sizeof(LL));
NTT(ln_t, len, ); NTT(ln_t2, len, );
for(int i = ; i < len; i++) b[i] = ln_t[i] * ln_t2[i] % MO;
NTT(b, len, -);
memset(b + n, , (len - n) * sizeof(LL));
ter(b, b, n);
return;
} void Exp(const LL *a, LL *b, int n) {
if(n == ) {
b[] = ;
b[] = ;
return;
}
Exp(a, b, n >> );
/// ans = b * (1 + a - ln b)
getLn(b, exp_t, n);
for(int i = ; i < n; i++) A[i] = (a[i] - exp_t[i]) % MO;
A[] = (A[] + ) % MO;
memset(A + n, , n * sizeof(LL));
memcpy(B, b, n * sizeof(LL)); memset(B + n, , n * sizeof(LL));
NTT(A, n << , ); NTT(B, n << , );
for(int i = ; i < (n << ); i++) b[i] = A[i] * B[i] % MO;
NTT(b, n << , -);
memset(b + n, , n * sizeof(LL));
return;
} inline void getExp(const LL *a, LL *b, int n) {
int len = ;
while(len < n) len <<= ;
Exp(a, b, len);
memset(b + n, , (len - n) * sizeof(LL));
return;
} inline void solve() {
if(Y == ) {
LL t = qpow(n, n - );
printf("%lld\n", t * t % MO);
return;
} LL z = (qpow(Y, MO - ) - ) % MO;
LL r = 1ll * n * n % MO * qpow(z, MO - ) % MO; pw[] = ;
for(int i = ; i <= n; i++) {
pw[i] = pw[i - ] * i % MO;
a[i] = qpow(i, i) * r % MO * qpow(pw[i], MO - ) % MO;
}
getExp(a, b, n + );
LL ans = b[n] * pw[n] % MO;
ans = ans * qpow(Y, n) % MO * qpow(z, n) % MO * qpow(n, MO - ) % MO;
printf("%lld\n", (ans + MO) % MO);
return;
}
} int main() { int f;
scanf("%d%lld%d", &n, &Y, &f);
if(f == ) {
t0::solve();
return ;
}
if(f == ) {
t1::solve();
return ;
}
t2::solve();
return ;
}
AC代码
以蒟蒻视角写了题解,以后还要继续努力!
感谢:

洛谷P5206 数树的更多相关文章
- 洛谷 P5206: bzoj 5475: LOJ 2983: [WC2019] 数树
一道技巧性非常强的计数题,历年WC出得最好(同时可能是比较简单)的题目之一. 题目传送门:洛谷P5206. 题意简述: 给定 \(n, y\). 一张图有 \(|V| = n\) 个点.对于两棵树 \ ...
- 洛谷1087 FBI树 解题报告
洛谷1087 FBI树 本题地址:http://www.luogu.org/problem/show?pid=1087 题目描述 我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为B串,全 ...
- 洛谷P3018 [USACO11MAR]树装饰Tree Decoration
洛谷P3018 [USACO11MAR]树装饰Tree Decoration树形DP 因为要求最小,我们就贪心地用每个子树中的最小cost来支付就行了 #include <bits/stdc++ ...
- 洛谷 P5206 - [WC2019]数树(集合反演+NTT)
洛谷题面传送门 神仙多项式+组合数学题,不过还是被我自己想出来了( 首先对于两棵树 \(E_1,E_2\) 而言,为它们填上 \(1\sim y\) 使其合法的方案数显然是 \(y\) 的 \(E_1 ...
- 洛谷P3372线段树1
难以平复鸡冻的心情,虽然可能在大佬眼里这是水题,但对蒟蒻的我来说这是个巨大的突破(谢谢我最亲爱的lp陪我写完,给我力量).网上关于线段树的题解都很玄学,包括李煜东的<算法竞赛进阶指南>中的 ...
- NOIP2017提高组Day2T3 列队 洛谷P3960 线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/9265380.html 题目传送门 - 洛谷P3960 题目传送门 - LOJ#2319 题目传送门 - Vij ...
- 洛谷P3703 [SDOI2017]树点涂色(LCT,dfn序,线段树,倍增LCA)
洛谷题目传送门 闲话 这是所有LCT题目中的一个异类. 之所以认为是LCT题目,是因为本题思路的瓶颈就在于如何去维护同颜色的点的集合. 只不过做着做着,感觉后来的思路(dfn序,线段树,LCA)似乎要 ...
- 洛谷P3830 随机树(SHOI2012)概率期望DP
题意:中文题,按照题目要求的二叉树生成方式,问(1)叶平均深度 (2)树平均深度 解法:这道题看完题之后完全没头绪,无奈看题解果然不是我能想到的qwq.题解参考https://blog.csdn.ne ...
- 洛谷 P3714 - [BJOI2017]树的难题(点分治)
洛谷题面传送门 咦?鸽子 tzc 竟然来补题解了?incredible( 首先看到这样类似于路径统计的问题我们可以非常自然地想到点分治.每次我们找出每个连通块的重心 \(x\) 然后以 \(x\) 为 ...
随机推荐
- RabbitMQ --- Hello Mr.Tua
目录 RabbitMQ --- Work Queues(工作队列) RabbitMQ --- Publish/Subscribe(发布/订阅) RabbitMQ --- Routing(路由) 安装环 ...
- 通过容器提交镜像(docker commit)以及推送镜像(docker push)笔记
在本地创建一个容器后,可以依据这个容器创建本地镜像,并可把这个镜像推送到Docker hub中,以便在网络上下载使用. 查看镜像 [root@docker-test1 ~]# docker image ...
- 熟记这些git命令,你就是大神
1.git log 查看 提交历史 默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面 2.git log -p -2 常用 -p 选项展开显示每次提交的内容差异 ...
- Scrum Meeting day 4
第四次会议 No_00:工作情况 No_01:任务说明 待完成 已完成 No_10:燃尽图 No_11:照片记录 待更新 No_100:代码/文档签入记录 No_101:出席表 ...
- BUAAMOOC项目终审报告
工作总结 我们是歪果仁带你灰开发团队.我们开发的项目是北航学堂(MOOC)的android客户端:BUAAMOOC. 目前我们完成了主要功能,包括UI设计,视频播放,视频下载,学习进度,个人信息等功能 ...
- 软件工程(五)UML
UML 统一建模语言,又称标准建模语言.是用来对软件密集系统进行可视化建模的一种语言.包括UML语义和UML表示法两个元素. UMl图由事物和关系组成,事物:UML模型中最基本的构成元素,是具有代表性 ...
- 四则运算安卓版ver.mk3
在原有的基础上做了些许改动以及添加了一点小功能,以下是代码: package com.example.add; import java.io.File; import com.example.add. ...
- (Alpha)Let's-版本发布说明
我们的Let’s APP发布了! (下载地址在“下载与安装”部分) Alpha版本功能 Alpha版本是我们发布的第一个版本,所以仅实现了活动实体和用户实体之间的基础联系功能. 基本功能 登录和注册 ...
- Windows查看端口被什么进程占用的简单方法----菜鸟养成
1. 还是因为同事告知Oracle的服务器连不上 最后发现改了端口就可以了, 但是很困惑 不知道为什么会这样,然后简单查了下: 命令 netstat -ano 查看监听的端口 baidu出来一个管道 ...
- Centos7 Journald 指令
Journald是为Linux服务器打造的新系统日志方式,它标志着文本日志文件的终结.现在日志信息写入到二进制文件,使用journalctl阅读,要获得这些信息,Linux管理员将需要一些实践. Re ...