[LOJ2983] [WC2019] 数树
题目链接
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\)为边集。
那么我们枚举树的形态可以得到答案为:
\]
其中\(E_2\)枚举的是边集,\(E_1\)是题目给出的边集。这个\(E_1\cap E_2\)不是很好办,我们可以利用下面这个容斥的式子化简:
\]
下面会给出证明,当然,读者自证不难。带进去可得:
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\)的树的个数,可以证明:
\]
证明下面会给出,其中,\(s\)被分成了\(k\)个联通块,大小分别为\(a_1,a_2\cdots a_k\)。
这套交换求和符号还是看得懂的吧...不然怎么敢来刚这个题
那么带进去可得:
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\),设一个幂级数:
\]
再设答案为\(g_i(x)\):
\]
其中\(F_i'\)表示求导。
那么背包转移就可以写成:
\]
那么可得:
\]
这里是照抄的乘积求导法则:
\]
换一下求和符号就是:
\]
然后令\(x=1\)得到:
\]
令\(t_i=F_i(1)=\prod_{j\in son_i}(g_j+F_j(x))\),那么把上面的式子抄下来:
\]
\]
然后\(O(n)\)递推就好了。
Subtask 2
我们照抄上面的式子:
\]
这里\(s\)枚举的是公共部分,所以\(g(s)\)要平方,因为两棵树都要满足。
展开:
\]
过程和上面一样。
我们换种方式枚举,枚举多少个联通块以及大小分别是多少,显然联通块无顺序所以要除以\(k!\),然后点有标号所以乘上组合数\(\frac{n!}{\prod a_i!}\),\(a\)个点的树个数为\(a^{a-2}\),写出来就是:
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}
\]
后面的卷积形式写成多项式就是:
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|\)有关的函数。
那么交换求和符号:
\]
\]
\]
可以发现等式左边右边都是\(f(s)\),证毕。
lemma 2:
我们要把\(k\)个联通块组成的森林连成一棵树的方案数,其中联通块大小分别为\(a_1,a_2\cdots a_k\)。
考虑每个联通块视为一个点,那么它的\(prufer\)序列共有\(k-2\)个点,每个点在\([1,k]\)。
我们枚举每个点是什么,统计方案数:
\]
其中\(b_i\)为第\(i\)位的数,\(c_i\)表示\(i\)出现了多少次。
交换求和符号:
&\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] 数树的更多相关文章
- [WC2019] 数树
[WC2019] 数树 Zhang_RQ题解(本篇仅概述) 前言 有进步,只做了半天.... 一道具有极强综合性的数数好题! 强大的多合一题目 精确地数学推导和耐心. 有套路又不失心意. 融合了: 算 ...
- 洛谷 P5206: bzoj 5475: LOJ 2983: [WC2019] 数树
一道技巧性非常强的计数题,历年WC出得最好(同时可能是比较简单)的题目之一. 题目传送门:洛谷P5206. 题意简述: 给定 \(n, y\). 一张图有 \(|V| = n\) 个点.对于两棵树 \ ...
- 并不对劲的bzoj5475:loj2983:p5206:[wc2019]数树
题目大意 task0:有两棵\(n\)(n\leq10^5)个点的树\(T1,T2\),每个点的点权可以是一个在\([1,y]\)里的数,如果两个点既在\(T1\)中有直接连边,又在\(T2\)中有直 ...
- 洛谷P5206 [WC2019]数树 [容斥,DP,生成函数,NTT]
传送门 Orz神仙题,让我长了许多见识. 长式子警告 思路 y=1 由于y=1时会导致后面一些式子未定义,先抓出来. printf("%lld",opt==0?1:(opt==1? ...
- 洛谷P5206 [WC2019] 数树(生成函数+容斥+矩阵树)
题面 传送门 前置芝士 矩阵树,基本容斥原理,生成函数,多项式\(\exp\) 题解 我也想哭了--orz rqy,orz shadowice 我们设\(T1,T2\)为两棵树,并定义一个权值函数\( ...
- 【LuoguP5206】[WC2019] 数树
题目链接 题意 定义 \(F(T_1,T_2)=y^{n-common}\) 其中 \(common\) 为两棵树 \(T_1,T_2\) 的公共边条数. 三种问题 1.给定 \(T_1,T_2\) ...
- 洛谷 P5206 - [WC2019]数树(集合反演+NTT)
洛谷题面传送门 神仙多项式+组合数学题,不过还是被我自己想出来了( 首先对于两棵树 \(E_1,E_2\) 而言,为它们填上 \(1\sim y\) 使其合法的方案数显然是 \(y\) 的 \(E_1 ...
- BZOJ5475 WC2019数树(prufer+容斥原理+树形dp+多项式exp)
因为一大堆式子实在懒得写题解了.首先用prufer推出CF917D用到的结论,然后具体见前言不搭后语的注释. #include<iostream> #include<cstdio&g ...
- [题解][P5206][WC2019] 数树 (op = 1)
简要题意 给定 \(n, y\). 一张图有 \(|V| = n\) 个点,现在给出两棵树 \(T_1=G(V, E_1)\) 和 \(T_2=G(V, E_2)\). 定义这两棵树的权值 \(F(E ...
随机推荐
- CF 914 D. Bash and a Tough Math Puzzle
D. Bash and a Tough Math Puzzle http://codeforces.com/contest/914/problem/D 题意: 单点修改,每次询问一段l~r区间能否去掉 ...
- SpringBoot 基于lettuce 连接池 配置redis多数据源操作 生产配置
添加pom<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons- ...
- onenet基础通信套件加B300移植
1. 遇到的第一个问题,说是少了文件,但是明明有这个文件的啊? scons: warning: Ignoring missing SConscript 'build_scons\arm\Hi2115\ ...
- 追书神器API
由于自己喜欢看小说,有的时候不方便手机看的时候希望在电脑上面看,但很多网站有广告啊,于是封装了套手机版的追书神器API 目前只做了搜索 详情 书评 换源 正文 调用方式: //搜索小说 var sea ...
- gitlab改root密码
1. ~$ sudo gitlab-rails console production 2.查询要改的用户 irb(main)::> u = User.where().first => #& ...
- hdu1527取石子游戏(威佐夫博弈)
取石子游戏 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submi ...
- 使用python中读取配置文件
最近在接触利用python来写测试框架,本人也是个刚接触python,所以是个小菜鸟,今天开始,一点点的记录学习中的积累,方便以后的学习以及回顾,也希望能帮助跟我一样的小菜鸟们一步步的成长起来.那么, ...
- 韦大仙python--购物车
程序:购物车程序 需求: 启动程序后,让用户输入工资,然后打印商品列表 允许用户根据商品编号购买商品 用户选择商品后,检测余额是否够,够就直接扣款,不够就提醒 可随时退出,退出时,打印已购买商品和余额 ...
- wordlist 4
wordlist 4 desolate 啥啥啥lete adj. 荒凉的:无人烟的 repression depression n. 抑制,[心理] 压抑:镇压 / n. 沮丧:忧愁:抑郁症: spe ...
- 【swiper】 滑块组件说明
swiper 滑块视图容器,其原型如下: <swiper indicator-dots="[Boolean]" indicator-color="[Color]&q ...