题目链接

LOJ:https://loj.ac/problem/2983

BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=5475

洛谷:https://www.luogu.org/problemnew/show/P5206

Soltion

超级毒瘤数数题...窝看了一晚上才看懂...

%%%rqy


subtask 0

很容易可以看出每个公共的边连成的联通块被绑一起了,所以答案就是\(y^{p}\),其中\(p\)为联通块个数。

也就是说答案为\(y^{n-m}\),其中\(m\)为公共边条数。


Subtask 1

设\(f(s)=y^{n-|s|}\),其中\(s\)为边集。

那么我们枚举树的形态可以得到答案为:

\[ans=\sum_{E_2}f(E_1\cap E_2)
\]

其中\(E_2\)枚举的是边集,\(E_1\)是题目给出的边集。这个\(E_1\cap E_2\)不是很好办,我们可以利用下面这个容斥的式子化简:

\[f(s)=\sum_{t\subseteq s}\sum_{p \subseteq t}(-1)^{|t|-|p|}f(p)
\]

下面会给出证明,当然,读者自证不难。带进去可得:

\[\begin{align}
ans&=\sum_{E_2}\sum_{t\subseteq E_1,t\subseteq E_2}\sum_{p\subseteq t}(-1)^{|t|-|p|}f(p)\\
&=\sum_{t\subseteq E_1} g(t)\sum_{p\subseteq t}(-1)^{|t|-|p|}f(p)\\
&=\sum_{t\subseteq E_1} g(t)\sum_{p\subseteq t}(-1)^{|t|-|p|}y^{n-|p|}\\
&=\sum_{t\subseteq E_1} g(t)y^{n-|t|}\sum_{p\subseteq t}(-1)^{|t|-|p|}y^{|t|-|p|}\\
&=\sum_{t\subseteq E_1} g(t)y^{n-|t|}\sum_{p\subseteq t}(-y)^{|t|-|p|}\\
&=\sum_{t\subseteq E_1} g(t)y^{n-|t|}(1-y)^{|t|}\\
\end{align}
\]

其中倒数第二个等式到最后一个等式用到了二项式定理,\(g(s)\)表示包含边集\(s\)的树的个数,可以证明:

\[g(s)=n^{k-2}\prod_{i=1}^ka_i
\]

证明下面会给出,其中,\(s\)被分成了\(k\)个联通块,大小分别为\(a_1,a_2\cdots a_k\)。

这套交换求和符号还是看得懂的吧...不然怎么敢来刚这个题

那么带进去可得:

\[\begin{align}
ans&=\sum_{t\subseteq E_1}\left(n^{k-2}\prod_{i=1}^ka_i\right)y^{k}(1-y)^{n-k}\\
&=\frac{(1-y)^n}{n^2}\sum_{t\subseteq E_1}\prod_{i=1}^{k}\frac{ny}{1-y}\cdot a_i
\end{align}
\]

至此,这玩意还是指数级的,但是我们可以发现,\(\frac{ny}{1-y}\)是固定的,设其为\(k\),也就是说,每个联通块有\(ka_i\)的贡献。

那么,其实我们就可以\(dp\)了,设\(f_{i,j}\)表示第\(i\)个点,只考虑子树\(i\)所在的联通块\(size\)为\(j\)的贡献,注意不考虑\(i\)这个联通块的贡献(这里贡献指的是若干个\(ka_i\)的乘积)。

然后直接暴力背包转移,时间复杂度降为了\(O(n^2)\)。

我们固定\(f_{i,j}\)的\(i\),设一个幂级数:

\[F_i(x)=\sum_{j=1}^nf_{i,j}x^j
\]

再设答案为\(g_i(x)\):

\[g_i(x)=\sum_{j=1}^{n}kj\cdot f_{i,j}=kF_i'(1)
\]

其中\(F_i'\)表示求导。

那么背包转移就可以写成:

\[F_i(x)=x\prod_{j\in son_i}(g_j+F_j(x))
\]

那么可得:

\[F_i'(x)=\prod_{j\in son_i}(g_j+F_j(x))+x\left(\prod_{j\in son_i}(g_j+F_j(x))\right)\left(\sum_{j\in son_i}\frac{F_j'(x)}{g_j+F_j(x)}\right)
\]

这里是照抄的乘积求导法则:

\[\left(\prod_{i=1}^kF_i(x)\right)'=\sum_{i=1}^{n}F_i'(x)\prod_{j\ne i}^kF_j(x)
\]

换一下求和符号就是:

\[\left(\prod_{i=1}^kF_i(x)\right)'=\prod_{i=1}^{k}F_i(x)\sum_{j=1}^{k}\frac{F_j'(x)}{F_j(x)}
\]

然后令\(x=1\)得到:

\[F_i'(1)=\prod_{j\in son_i}(g_j+F_j(1))+\left(\prod_{j\in son_i}(g_j+F_j(1))\right)\left(\sum_{j\in son_i}\frac{F_j'(1)}{g_j+F_j(1)}\right)
\]

令\(t_i=F_i(1)=\prod_{j\in son_i}(g_j+F_j(x))\),那么把上面的式子抄下来:

\[g_i=F_i'(1)=t_i(1+\sum_{j\in son_i}\frac{g_j}{g_j+t_j})
\]

\[t_i=\prod_{j\in son}g_j+t_j
\]

然后\(O(n)\)递推就好了。


Subtask 2

我们照抄上面的式子:

\[ans=\sum_{s}y^{n-|s|}(1-y)^{|s|}g^2(s)
\]

这里\(s\)枚举的是公共部分,所以\(g(s)\)要平方,因为两棵树都要满足。

展开:

\[ans=\frac{(1-y)^n}{n^4}\sum_{s}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)a_i^2
\]

过程和上面一样。

我们换种方式枚举,枚举多少个联通块以及大小分别是多少,显然联通块无顺序所以要除以\(k!\),然后点有标号所以乘上组合数\(\frac{n!}{\prod a_i!}\),\(a\)个点的树个数为\(a^{a-2}\),写出来就是:

\[\begin{align}
ans&=\frac{(1-y)^n}{n^4}\sum_{k=1}^n\frac{1}{k!}\sum_{a_1+a_2\cdots a_k=n}\frac{n!}{\prod a_i!}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)a_i^2\cdot a_i^{a_i-2}\\
&=\frac{(1-y)^nn!}{n^4}\sum_{k=1}^n\frac{1}{k!}\sum_{a_1+a_2\cdots a_k=n}\prod_{i=1}^{k}\left(\frac{n^2y}{1-y}\right)\frac{a_i^{a_i}}{a_i!}\\
\end{align}
\]

后面的卷积形式写成多项式就是:

\[\begin{align}
ans&=\frac{(1-y)^nn!}{n^4}[x^n]\sum_{k=1}^n\frac{1}{k!}\left(\sum_{a=1}^{+\infty}\frac{n^2y}{1-y}\cdot \frac{a^a}{a!}x^a\right)^k\\
\end{align}
\]

注意到这是个多项式\(\exp\)形式,直接算就好了,复杂度\(O(n\log n)\)。

其实多项式\(\exp\)的组合意义就是带标号的多重背包,所以上面符合多项式\(\exp\)的式子也就不出意外了。


上面lemma的proof

lemma 1

\[f(s)=\sum_{t\subseteq s}\sum_{p \subseteq t}(-1)^{|t|-|p|}f(p)
\]

其中\(f\)是一个和\(|s|\)有关的函数。

那么交换求和符号:

\[f(s)=\sum_{p \subseteq s}f(p)\sum_{p\subseteq t,t\subseteq s}(-1)^{|t|-|p|}
\]

\[f(s)=\sum_{p \subseteq s}f(p)\sum_{t\subseteq s-p}(-1)^{|t|}
\]

\[f(s)=\sum_{p \subseteq s}f(p)[s=p]
\]

可以发现等式左边右边都是\(f(s)\),证毕。


lemma 2:

我们要把\(k\)个联通块组成的森林连成一棵树的方案数,其中联通块大小分别为\(a_1,a_2\cdots a_k\)。

考虑每个联通块视为一个点,那么它的\(prufer\)序列共有\(k-2\)个点,每个点在\([1,k]\)。

我们枚举每个点是什么,统计方案数:

\[\sum_{1\leqslant b_1,b_2\cdots b_{k-2}\leqslant k}\prod_{i=1}^{k}a_i^{c_i+1}
\]

其中\(b_i\)为第\(i\)位的数,\(c_i\)表示\(i\)出现了多少次。

交换求和符号:

\[\begin{align}
&\left(\prod_{i=1}^ka_i\right)\sum_{1\leqslant b_1,b_2\cdots b_{k-2}\leqslant k}\prod_{i=1}^{k}a_i^{c_i}\\
=&\left(\prod_{i=1}^ka_i\right)\prod_{i=1}^{k-2}\sum_{x=1}^{k}a_{x}\\
=&\left(\prod_{i=1}^ka_i\right)\prod_{i=1}^{k-2}n\\
=&n^{n-2}\prod_{i=1}^ka_i\\
\end{align}
\]


注意当\(y=1\)时上面很多式子都没有意义,需要特判。

#include<bits/stdc++.h>
using namespace std; void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
} void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');} #define lf double
#define ll long long
#define mp make_pair
#define fr first
#define sc second const int maxn = 6e5+10;
const int inf = 1e9;
const lf eps = 1e-8;
const int mod = 998244353; int add(int x,int y) {return x+y>mod?x+y-mod:x+y;}
int del(int x,int y) {return x-y<0?x-y+mod:x-y;}
int mul(int x,int y) {return 1ll*x*y-1ll*x*y/mod*mod;} int n,y; int qpow(int a,int x) {
int res=1;
for(;x;x>>=1,a=mul(a,a)) if(x&1) res=mul(res,a);
return res;
} namespace fuckpps {
void solve(int op) {
if(op==0) write(1);
else if(op==1) write(qpow(n,n-2));
else write(qpow(n,2*n-4));
}
} namespace subtask0 {
void solve() {
map<pair<int,int >,int > s;int ans=0;
for(int i=1,x,yy;i<n;i++) read(x),read(yy),s[mp(min(x,yy),max(x,yy))]=1;
for(int i=1,x,yy;i<n;i++) read(x),read(yy),ans+=s[mp(min(x,yy),max(x,yy))];
write(qpow(y,n-ans));
}
} namespace subtask1 {
int head[maxn],tot,g[maxn],t[maxn],k;
struct edge{int to,nxt;}e[maxn<<1]; void ad(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
void ins(int u,int v) {ad(u,v),ad(v,u);} void dfs(int x,int fa) {
g[x]=k,t[x]=1;
for(int i=head[x],v;i;i=e[i].nxt)
if((v=e[i].to)!=fa) {
dfs(v,x);
g[x]=add(g[x],mul(g[v],qpow(add(g[v],t[v]),mod-2)));
t[x]=mul(t[x],add(g[v],t[v]));
}
g[x]=mul(g[x],t[x]);
} void solve() {
k=mul(mul(n,y),qpow(1-y+mod,mod-2));
for(int i=1,u,v;i<n;i++) read(u),read(v),ins(u,v);
dfs(1,0);write(mul(g[1],mul(qpow(1-y+mod,n),qpow(mul(n,n),mod-2))));
}
} namespace subtask2 {
int w[maxn],rw[maxn],N,bit,pos[maxn],f[maxn],fac[maxn],ifac[maxn],inv[maxn],mxn,g[maxn],K;
int tmp[7][maxn]; void ntt_init() {
w[0]=rw[0]=1,w[1]=qpow(3,(mod-1)/N);mxn=N;
for(int i=2;i<=N;i++) w[i]=mul(w[i-1],w[1]);
rw[1]=qpow(w[1],mod-2);
for(int i=2;i<=N;i++) rw[i]=mul(rw[i-1],rw[1]);
} void ntt(int *r,int op) {
for(int i=1;i<N;i++) if(pos[i]>i) swap(r[i],r[pos[i]]);
for(int i=1,d=mxn>>1;i<N;i<<=1,d>>=1)
for(int j=0;j<N;j+=i<<1)
for(int k=0;k<i;k++) {
int x=r[j+k],y=mul((op==1?w:rw)[k*d],r[i+j+k]);
r[j+k]=add(x,y),r[i+j+k]=del(x,y);
}
if(op==-1) {int d=qpow(N,mod-2);for(int i=0;i<N;i++) r[i]=mul(r[i],d);}
} void ntt_get(int len) {
for(N=1,bit=0;N<=len;N<<=1,bit++) ;
for(int i=0;i<N;i++) pos[i]=pos[i>>1]>>1|((i&1)<<(bit-1));
} void poly_inv(int *r,int *t,int len) {
if(len==1) return t[0]=qpow(r[0],mod-2),void();
poly_inv(r,t,len>>1);
for(int i=0;i<len>>1;i++) tmp[0][i]=t[i],tmp[1][i]=r[i];
for(int i=len>>1;i<len;i++) tmp[0][i]=0,tmp[1][i]=r[i];
ntt_get(len),ntt(tmp[0],1),ntt(tmp[1],1);
for(int i=0;i<N;i++) t[i]=del(mul(2,tmp[0][i]),mul(mul(tmp[1][i],tmp[0][i]),tmp[0][i]));
ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
for(int i=0;i<len<<1;i++) tmp[0][i]=tmp[1][i]=0;
} void poly_der(int *r,int *t,int len) {
ntt_get(len);
for(int i=1;i<len;i++) t[i-1]=mul(i,r[i]);
for(int i=len-1;i<N;i++) t[i]=0;
} void poly_int(int *r,int *t,int len) {
ntt_get(len);
for(int i=0;i<len;i++) t[i+1]=mul(inv[i+1],r[i]);t[0]=0;
for(int i=len+1;i<N;i++) t[i]=0;
} void poly_ln(int *r,int *t,int len) {
poly_der(r,tmp[2],len);
poly_inv(r,tmp[3],len);
ntt_get(len),ntt(tmp[2],1),ntt(tmp[3],1);
for(int i=0;i<N;i++) tmp[3][i]=mul(tmp[2][i],tmp[3][i]);
ntt(tmp[3],-1);
poly_int(tmp[3],t,len);
for(int i=0;i<len<<1;i++) tmp[3][i]=0;
} void poly_exp(int *r,int *t,int len) {
if(len==1) return t[0]=1,void();
poly_exp(r,t,len>>1);
for(int i=0;i<len>>1;i++) tmp[4][i]=r[i],tmp[5][i]=t[i];
for(int i=len>>1;i<len;i++) tmp[4][i]=r[i],tmp[5][i]=0;
poly_ln(tmp[5],tmp[6],len);
for(int i=0;i<len;i++) tmp[4][i]=del(tmp[4][i],tmp[6][i]);
tmp[4][0]=add(tmp[4][0],1);
ntt_get(len),ntt(tmp[4],1),ntt(tmp[5],1);
for(int i=0;i<N;i++) t[i]=mul(tmp[4][i],tmp[5][i]);
ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
} void solve() {
for(N=1,bit=0;N<=n<<2;N<<=1,bit++);ntt_init();
inv[0]=fac[0]=ifac[0]=inv[1]=1;K=mul(mul(n,mul(n,y)),qpow(1-y+mod,mod-2));
for(int i=2;i<N;i++) inv[i]=mul(mod-mod/i,inv[mod%i]);
for(int i=1;i<N;i++) fac[i]=mul(fac[i-1],i);
for(int i=1;i<N;i++) ifac[i]=mul(ifac[i-1],inv[i]);
for(int i=1;i<=n;i++) f[i]=mul(K,mul(qpow(i,i),ifac[i]));
ntt_get(n);poly_exp(f,g,N);
write(mul(g[n],mul(mul(qpow(1-y+mod,n),fac[n]),qpow(qpow(n,4),mod-2))));
}
} int main() {
read(n),read(y);int op;read(op);
if(y==1) fuckpps :: solve(op);
else if(op==0) subtask0 :: solve();
else if(op==1) subtask1 :: solve();
else subtask2 :: solve();
return 0;
}

[LOJ2983] [WC2019] 数树的更多相关文章

  1. [WC2019] 数树

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

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

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

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

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

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

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

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

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

  6. 【LuoguP5206】[WC2019] 数树

    题目链接 题意 定义 \(F(T_1,T_2)=y^{n-common}\) 其中 \(common\) 为两棵树 \(T_1,T_2\) 的公共边条数. 三种问题 1.给定 \(T_1,T_2\) ...

  7. 洛谷 P5206 - [WC2019]数树(集合反演+NTT)

    洛谷题面传送门 神仙多项式+组合数学题,不过还是被我自己想出来了( 首先对于两棵树 \(E_1,E_2\) 而言,为它们填上 \(1\sim y\) 使其合法的方案数显然是 \(y\) 的 \(E_1 ...

  8. BZOJ5475 WC2019数树(prufer+容斥原理+树形dp+多项式exp)

    因为一大堆式子实在懒得写题解了.首先用prufer推出CF917D用到的结论,然后具体见前言不搭后语的注释. #include<iostream> #include<cstdio&g ...

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

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

随机推荐

  1. C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 – 员工离职管理

    C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 – 员工离职管理 当公司有几万人,上千家加盟网点,几个庞大的直属分公司后,系统账户的有效管理也是一个头疼的问题,把所有的帐户及时进行科学 ...

  2. CentOS 7.2-编译安装zabbix 3.4

    起因: 前面已经使用yum安装了zabbix 3.4了,准备去交差了,交差时老大明确要求必须使用编译安装,统一放在/usr/local目录下.... 重来吧!! 一.环境说明 本次安装使用CentOS ...

  3. Objective-C 第一个小程序

    示例一 (类似C) //1.代码编写 //跟C语言一样,OC程序的入口依然是main函数,只不过写到一个.m文件中.比如这里写到一个main.m文件中(文件名可以是中文) #include <s ...

  4. 初学Direct X(4)

    初学Direct X(4) 本文学着做出一个如下的小游戏 游戏方式是使用键盘控制红色的Bucket收集蓝色的炸弹 1.酝酿一下 现在我已经掌握: 将位图文件加载到内存 绘制位图到buckbuffer ...

  5. Python的包(Packages)

    包,Package,是一种Python模块的集合,从文件组织形式上看,包就是一个文件夹,里面放着各种模块(.py文件),也可以有子文件夹(子包).包名构建了一个Python模块的命名空间.比如,模块名 ...

  6. 【WXS全局对象】Date

    属性: 名称 说明 Date.parse( [dateString] ) 解析一个日期时间字符串,并返回 1970/1/1 午夜距离该日期时间的毫秒数. Date.UTC(year,month,day ...

  7. HDU 1403 Longest Common Substring(后缀自动机——附讲解 or 后缀数组)

    Description Given two strings, you have to tell the length of the Longest Common Substring of them. ...

  8. 蓝牙ble数据转语音实现Android AudioRecord方法推荐

    蓝牙ble数据转语音实现Android AudioRecord方法推荐 教程  欢迎走进zozo的学习之旅. 概述 蓝牙BLE又称bluetooth smart,主打的是低功耗和快速链接,所以在支持的 ...

  9. default & delete

    一.使用“=default” 1. 显式生成拷贝控制成员的合成版本 class A { public: A() = default; A(const A &) = default; A& ...

  10. 自测之Lesson9:时钟与信号

    题目一:编写一个获取当前时间的程序,并将其以“year-mon-day time”的形式输出. 程序代码: #include <stdio.h> #include <time.h&g ...