洛谷题面传送门

神仙多项式+组合数学题,不过还是被我自己想出来了(

首先对于两棵树 \(E_1,E_2\) 而言,为它们填上 \(1\sim y\) 使其合法的方案数显然是 \(y\) 的 \(E_1\cap E_2\) 的连通块次方,又显然 \(E_1,E_2\) 的导出子图是一棵森林,因此 \(E_1\cap E_2\) 连通块个数就是 \(n-|E_1\cap E_2|\),因此我们要求的答案就是 \(\sum\limits_{E_1}\sum\limits_{E_2}y^{n-|E_1\cap E_2|}\)。

考虑对三个 subtask 分别讨论一下,首先是 \(op=0\)​​,这个就非常 trivial 了,直接暴力用个 setmap 之类的东西求解两棵树的交集,白送 6pts(对于非 jxd 选手就白送了 28pts)。

namespace sub0{
map<int,bool> vis[MAXN+5];
void solve(){
int sum=0;
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),vis[min(u,v)][max(u,v)]=1;
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),sum+=vis[min(u,v)][max(u,v)];
printf("%d\n",qpow(y,n-sum));
}
}

接下来考虑 \(op=1\)​ 的情况,注意到这里涉及 \(E_1\)​​ 与 \(E_2\)​ 的交集,而这是不太好直接枚举的,因此考虑按照 LOJ #3399 的讨论进行集合反演,即根据公式 \(f(S)=\sum\limits_{T\subseteq S}f(T)\sum\limits_{T\subseteq S'\subseteq S}(-1)^{|S'|-|T|}\)​,枚举 \(S\subseteq E_1\cap E_2\)​,再枚举 \(S\subseteq T\subseteq E_1\cap E_2\)​,这样我们就可以像莫比乌斯反演那样枚举所有满足 \(T\)​ 是 \(E_2\)​ 边集的一个子集的 \(E_2\)​,具体来说,

\[\begin{aligned}
ans&=\sum\limits_{E_2}y^{n-|E_1\cap E_2|}\\
&=y^n\sum\limits_{E_2}\sum\limits_{S\subseteq E_1\cap E_2}(\dfrac{1}{y})^{|S|}\sum\limits_{T,s.t.S\subseteq T\subseteq E_1\cap E_2}(-1)^{|T|-|S|}\\
&=y^n\sum\limits_{T\subseteq E_1}(\sum\limits_{S\subseteq T}(\dfrac{1}{y})^{|S|}(-1)^{|T|-|S|})\sum\limits_{E_2}[T\subseteq E_2]\\
&=y^n\sum\limits_{T\subseteq E_1}(\sum\limits_{x=0}^{|T|}\dbinom{|T|}{x}(\dfrac{1}{y})^{x}(-1)^{|T|-x})g(T)\\
&=y^n\sum\limits_{T\subseteq E_1}(\dfrac{1-y}{y})^{|T|}g(T)
\end{aligned}
\]

其中 \(g(T)=\sum\limits_{E_2}[T\subseteq E_2]\),也就是有多少棵树 \(E_2\) 满足 \(T\) 是 \(E_2\)​ 的子集。根据我们幼儿园就学过的结论,\(g(T)=n^{k-2}\prod\limits_{i=1}^ka_i\),其中 \(k\) 为 \(T\) 形成的连通块个数,\(a_1,a_2,\cdots,a_k\) 分别表示 \(T\) 形成的 \(k\)​ 个连通块的大小。推到这里,式子似乎已经无法继续再化下去,因此考虑组合意义,具体来说我们可以将考虑这样这一个过程:我们在树上选出若干条边,每选出一条边权值乘上 \(\dfrac{1-y}{y}\)​,然后在这些边组成的每个连通块中放上一个球,每放上一个球权值乘上 \(n\),最后所有方案的权值之和乘上 \(\dfrac{y^n}{n^2}\) 就是答案。

考虑树形 DP,\(dp_{i,0/1}\) 表示目前考虑了 \(i\) 的子树,目前 \(i\) 所在的连通块是否放上了一个球(\(0/1\))的所有放置方案的权值之和,合并两个子树 \(x,y\),其中 \(x\) 是 \(y\) 的父亲时就分 \(x\) 所在的连通块原来是否放上一个球,以及 \(x,y\) 之间的边是否被选择即可。

时间复杂度 \(\mathcal O(n)\)

namespace sub1{
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0,coef,ivn;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dp[MAXN+5][2],tmp[2];
void add(int &x,int v){((x+=v)>=MOD)&&(x-=MOD);}
void dfs(int x,int f){
dp[x][0]=1;dp[x][1]=n;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;dfs(y,x);
memset(tmp,0,sizeof(tmp));
for(int p=0;p<2;p++) for(int q=0;q<2;q++)
if(!(p&q)) add(tmp[p|q],1ll*dp[x][p]*dp[y][q]%MOD*coef%MOD);
for(int p=0;p<2;p++) add(tmp[p],1ll*dp[x][p]*dp[y][1]%MOD);
for(int p=0;p<2;p++) dp[x][p]=tmp[p];
} //for(int i=0;i<2;i++) printf("%d %d %d\n",x,i,dp[x][i]);
}
void solve(){
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
coef=(MOD-1ll*(y-1)*qpow(y,MOD-2)%MOD)%MOD;dfs(1,0);
// printf("%d %d\n",coef,ivn);
printf("%d\n",1ll*dp[1][1]*qpow(y,n)%MOD*qpow(n,MOD-3)%MOD);
}
}

最后是 \(op=2\) 的情况,此时两棵树都没有给定,不过我们还是可以套用集合反演公式枚举上文中的 \(T\),类似地有

\[ans=y^n\sum\limits_{T}(\dfrac{1-y}{y})^{|T|}g^2(T)
\]

直接枚举 \(T\) 显然是不行的。考虑枚举 \(T\) 中连通块的个数 \(k\),这样可以得到:

\[\begin{aligned}
ans&=y^n\sum\limits_{k=1}^n\sum\limits_{a_1+a_2+\cdots+a_k=n,a_i\le a_{i+1}}h(a_1,a_2,\cdots,a_k)(\dfrac{1-y}{y})^{n-k}(n^{k-2}\prod\limits_{i=1}^ka_i)^2
\end{aligned}
\]

其中 \(h(a_1,a_2,\cdots,a_k)\) 表示有多少个森林满足其每个连通块的大小分别为 \(a_1,a_2,\cdots,a_k\)。考虑怎么求 \(h(a_1,a_2,\cdots,a_k)\)​,首先我们选出这 \(k\) 连通块的点集,方案数可以写成一个多重组合数的形式,即 \(\dbinom{n}{a_1,a_2,\cdots,a_k}=\dfrac{n!}{a_1!a_2!\cdots a_k!}\),其次这 \(k\) 个连通块任意排列得到的图都是相同的,方案数除以 \(k!\),再其次,每个连通块中连边方案为 \(\prod\limits_{i=1}^ka_i^{a_i-2}\),因此

\[h(a_1,a_2,\cdots,a_k)=\dfrac{n!}{\prod\limits_{i=1}^ka_i!}·\dfrac{1}{k!}·\prod\limits_{i=1}^ka_i^{a_i-2}
\]

带回去:

\[ans=y^n\sum\limits_{k=1}^n\sum\limits_{a_1+a_2+\cdots+a_k=n,a_i\le a_{i+1}}\dfrac{n!}{\prod\limits_{i=1}^ka_i!}·\dfrac{1}{k!}·\prod\limits_{i=1}^ka_i^{a_i-2}(\dfrac{1-y}{y})^{n-k}(n^{k-2}\prod\limits_{i=1}^ka_i)^2
\]

化简一下:

\[ans=y^nn!\dfrac{1}{n^4}\sum\limits_{k=1}^n\sum\limits_{a_1+a_2+\cdots+a_k=n,a_i\le a_{i+1}}\dfrac{1}{\prod\limits_{i=1}^ka_i!}·\dfrac{1}{k!}·(\dfrac{1-y}{y})^{n-k}n^{2k}\prod\limits_{i=1}^ka_i^{a_i}
\]
\[ans=\dfrac{y^nn!}{n^4}·(\dfrac{1-y}{y})^n·\sum\limits_{k=1}^n\sum\limits_{a_1+a_2+\cdots+a_k=n,a_i\le a_{i+1}}\dfrac{1}{k!}·(\dfrac{y}{1-y})^k·n^{2k}·\prod\limits_{i=1}^k\dfrac{a_i^{a_i}}{a_i!}
\]

如果我们设 \(F(x)=\sum\limits_{n\ge 0}\dfrac{n^n}{n!}x^n\),那么不难发现后面的 \(\sum\) 可以写成

\[\sum\limits_{k=1}^n\dfrac{(\dfrac{y}{1-y})^k·n^{2k}·[x^n]F^k(x)}{k!}
\]

不难发现 \(k=0\) 和 \(k>n\) 时贡献都是 \(0\),因此如果设 \(G(x)=\dfrac{y}{1-y}·n^2·F(x)\),那么式子可写作

\[\sum\limits_{k}\dfrac{G^k(x)}{k!}
\]

然后我们发现这就是

\[\exp G
\]

于是 \(ans=\dfrac{y^nn!}{n^4}·(\dfrac{1-y}{y})^n·[x^n]\exp G\),多项式 \(\exp\) 一下即可,时间复杂度 \(n\log n\)。

namespace sub2{
const int pr=3;
const int ipr=332748118;
const int MAXP=1<<19;
int rev[MAXP+5],inv[MAXP+5],fac[MAXP+5],ifac[MAXP+5];
void init_fac(int n){
for(int i=(fac[0]=ifac[0]=inv[0]=inv[1]=1)+1;i<=MAXP;i++) inv[i]=1ll*inv[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]*inv[i]%MOD;
}
void NTT(vector<int> &a,int len,int type){
int lg=31-__builtin_clz(len);
for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<lg-1);
for(int i=0;i<len;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=2;i<=len;i<<=1){
int W=qpow((type<0)?ipr:pr,(MOD-1)/i);
for(int j=0;j<len;j+=i){
for(int k=0,w=1;k<(i>>1);k++,w=1ll*w*W%MOD){
int X=a[j+k],Y=1ll*w*a[(i>>1)+j+k]%MOD;
a[j+k]=(X+Y)%MOD;a[(i>>1)+j+k]=(X-Y+MOD)%MOD;
}
}
} if(!~type){
int ivn=qpow(len,MOD-2);
for(int i=0;i<len;i++) a[i]=1ll*a[i]*ivn%MOD;
}
}
vector<int> conv(vector<int> a,vector<int> b){
int LEN=1;while(LEN<a.size()+b.size()) LEN<<=1;
a.resize(LEN,0);b.resize(LEN,0);NTT(a,LEN,1);NTT(b,LEN,1);
for(int i=0;i<LEN;i++) a[i]=1ll*a[i]*b[i]%MOD;
NTT(a,LEN,-1);return a;
}
vector<int> conv(vector<int> a,vector<int> b,int mxl){
int LEN=1;while(LEN<a.size()+b.size()) LEN<<=1;
a.resize(LEN,0);b.resize(LEN,0);NTT(a,LEN,1);NTT(b,LEN,1);
for(int i=0;i<LEN;i++) a[i]=1ll*a[i]*b[i]%MOD;
NTT(a,LEN,-1);while(a.size()>mxl) a.ppb();return a;
}
vector<int> getinv(vector<int> a,int len){
vector<int> b(len);b[0]=qpow(a[0],MOD-2);
for(int i=2;i<=len;i<<=1){
// printf("inv %d\n",i);
vector<int> c(a.begin(),a.begin()+i);
vector<int> d(b.begin(),b.begin()+i);
d=conv(d,d);c=conv(c,d);
for(int j=0;j<i;j++) b[j]=(2ll*b[j]-c[j]+MOD)%MOD;
} return b;
}
vector<int> direv(vector<int> a,int len){
vector<int> b(len);
for(int i=1;i<len;i++) b[i-1]=1ll*a[i]*i%MOD;
return b;
}
vector<int> inter(vector<int> a,int len){
vector<int> b(len);
for(int i=1;i<len;i++) b[i]=1ll*a[i-1]*inv[i]%MOD;
return b;
}
vector<int> getln(vector<int> a,int len){
// printf("%d\n",len);
vector<int> b=getinv(a,len),_b=direv(a,len);
b=conv(b,_b);b=inter(b,len);return b;
}
vector<int> getexp(vector<int> a,int len){
vector<int> b(len);b[0]=1;
for(int i=2;i<=len;i<<=1){
// printf("exp %d\n",i);
vector<int> c(b.begin(),b.begin()+i);
vector<int> d(b.begin(),b.begin()+i);
d=getln(d,i);
for(int j=0;j<i;j++) d[j]=(a[j]-d[j]+MOD)%MOD;
d[0]=(d[0]+1)%MOD;d=conv(c,d);
for(int j=0;j<i;j++) b[j]=d[j];
} return b;
}
void solve(){
if(y==1) return printf("%d\n",1ll*qpow(n,MOD-3+n)*qpow(n,MOD-3+n)%MOD),void();
init_fac(MAXP);
int LEN=1;while(LEN<=n) LEN<<=1;vector<int> vec(LEN);
int coef=1ll*n*n%MOD*y%MOD*qpow((1-y+MOD)%MOD,MOD-2)%MOD;
for(int i=1;i<LEN;i++) vec[i]=1ll*coef*qpow(i,i)%MOD*ifac[i]%MOD;
// for(int i=0;i<LEN;i++) printf("%d\n",vec[i]);
// puts("-1");
vec=getexp(vec,LEN);
// for(int i=0;i<LEN;i++) printf("%d\n",vec[i]);
printf("%d\n",1ll*qpow(n,MOD-5)%MOD*vec[n]%MOD*qpow((1-y+MOD)%MOD,n)%MOD*fac[n]%MOD);
}
}

洛谷 P5206 - [WC2019]数树(集合反演+NTT)的更多相关文章

  1. 洛谷P5206 [WC2019]数树 [容斥,DP,生成函数,NTT]

    传送门 Orz神仙题,让我长了许多见识. 长式子警告 思路 y=1 由于y=1时会导致后面一些式子未定义,先抓出来. printf("%lld",opt==0?1:(opt==1? ...

  2. 洛谷P5206 [WC2019] 数树(生成函数+容斥+矩阵树)

    题面 传送门 前置芝士 矩阵树,基本容斥原理,生成函数,多项式\(\exp\) 题解 我也想哭了--orz rqy,orz shadowice 我们设\(T1,T2\)为两棵树,并定义一个权值函数\( ...

  3. 并不对劲的bzoj5475:loj2983:p5206:[wc2019]数树

    题目大意 task0:有两棵\(n\)(n\leq10^5)个点的树\(T1,T2\),每个点的点权可以是一个在\([1,y]\)里的数,如果两个点既在\(T1\)中有直接连边,又在\(T2\)中有直 ...

  4. [题解][P5206][WC2019] 数树 (op = 1)

    简要题意 给定 \(n, y\). 一张图有 \(|V| = n\) 个点,现在给出两棵树 \(T_1=G(V, E_1)\) 和 \(T_2=G(V, E_2)\). 定义这两棵树的权值 \(F(E ...

  5. 洛谷 P5206: bzoj 5475: LOJ 2983: [WC2019] 数树

    一道技巧性非常强的计数题,历年WC出得最好(同时可能是比较简单)的题目之一. 题目传送门:洛谷P5206. 题意简述: 给定 \(n, y\). 一张图有 \(|V| = n\) 个点.对于两棵树 \ ...

  6. [WC2019] 数树

    [WC2019] 数树 Zhang_RQ题解(本篇仅概述) 前言 有进步,只做了半天.... 一道具有极强综合性的数数好题! 强大的多合一题目 精确地数学推导和耐心. 有套路又不失心意. 融合了: 算 ...

  7. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  8. 【BZOJ2830/洛谷3830】随机树(动态规划)

    [BZOJ2830/洛谷3830]随机树(动态规划) 题面 洛谷 题解 先考虑第一问. 第一问的答案显然就是所有情况下所有点的深度的平均数. 考虑新加入的两个点,一定会删去某个叶子,然后新加入两个深度 ...

  9. 洛谷P1102 A-B数对

    洛谷P1102 A-B数对 https://www.luogu.org/problem/show?pid=1102 题目描述 出题是一件痛苦的事情! 题目看多了也有审美疲劳,于是我舍弃了大家所熟悉的A ...

随机推荐

  1. Java---String和StringBuffer类

    Java---String和StringBuffer类 Java String 类 字符串在Java中属于对象,Java提供String类来创建和操作字符串. 创建字符串 创建字符串常用的方法如下: ...

  2. STM32中操作寄存器GPIOB_CRL &= ~( 0x0F<< (4*0))与GPIOB_CRL &=~(0x0F)之间有什么区别吗?

    没有区别,作用相同.只是这样写便于修改和沿用. 对于只用到PB0端口的程序~(0x0f << (4*0)) 和~0x0f没有区别.0x0f <<(4*N) 就是 向左 移动N个 ...

  3. js--Symbol 符号基本数据类型

    前言 ECMAScript 6 中新增了 Symbol 符号这一基本数据类型,那么Symbol 是用来干什么的,对开发又有什么帮助呢?本文来总结记录一下 Symbol 的相关知识点. 正文 Symbo ...

  4. JVM:类加载与字节码技术-2

    JVM:类加载与字节码技术-2 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 内容 这部分内容在上一篇笔记中: 类文件结构 字节码指令 编译期处理 类加载阶段 ...

  5. OO_JAVA_四个单元的总结

    总结本单元两次作业的架构设计 设计目标 尽量减少特殊容器的存在,能通用就通用,减少重复的类同代码. 基础容器的存在,就是为上述目标而服务的. 设计概要 底层:基础的.类型无关.无依赖的容器以及对应的查 ...

  6. proto3语法记录

    protobuf 是谷歌的语言无关,平台无关,可扩展的,高效的结构化数据序列化机制,比xml和json的序列化的速度更快,此处记录一下 proto3 的语法,防止以后忘记. 注意:proto3 语法需 ...

  7. 转帖:新版vivado2019.2新增增量综合功能

    从 Vivado 2019.1 版本开始,Vivado 综合引擎就已经可以支持增量流程了.这使用户能够在设计变化较小时减少总的综合运行时间. Vivado IDE 和 Tcl 命令批处理模式都可以启用 ...

  8. Python3 装逼神器---词云(wordcloud)

    词云 (Word Cloud)是对文本中出现频率较高的词语给予视觉化展示的图形, 是一种常见的文本挖掘的方法. 实例:     依赖包: # pip3 install wordcloud  jieba ...

  9. Python3使用Print输出彩色字体

    一.介绍 在一些开发程序中,有些输出消息需要突出显示,我们可以尝试着给他们换上更靓丽的颜色来突出显示. 二.实现过程 终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关. ...

  10. oracle 归档日志:db_recovery_file_dest、log_archive_dest和log_archive_dest_n的区别和使用

    概念: db_recovery_file_dest:默认的指定闪回恢复区路径 log_archive_dest:指定归档文件存放的路径,所有归档路径必须是本地的,默认为''.log_archive_d ...