[LibreOJ #2983]【WC2019】数树【计数】【DP】【多项式】
Description
此题含有三个子问题
问题1:
给出n个点的两棵树,记m为只保留同时在两棵树中的边时连通块的个数,求\(y^m\)
问题2:
给出n个点的一棵树,另外一棵树任意生成,求所有方案总的\(y^m\)的和
问题3:
两棵树均任意生成,求所有方案总的\(y^m\)的和
n<=100000,答案对998244353取模
Solution
问题1:
求出同时在两棵树中的边数v,容易得到m=n-v,直接算即可。
问题2:
记\(z=y^{-1}\)即y在模意义下的逆元,我们就是要求\(z^v\)的总和
直接计算比较困难
考虑这样一个转化\(z^v=\sum\limits_{i=0}^{v}(z-1)^i{v\choose i}\)
考虑它的组合意义,实际上就是我们任意枚举一个重合边集E的子集E0,贡献就是\((z-1)^{|E_0|}\)
我们不妨枚举原树边集的子集\(E_0\),再计算有多少个生成树包含\(E_0\)
假设\(E_0\)构成了m个连通块,它们的点数分别是\(a_1,a_2,...,a_m\)
那么包含\(E_0\)的生成树个数就是\(n^{m-2}\prod\limits_{i=1}^{m} a_i\),总的贡献就是\((z-1)^{n-m}n^{m-2}\prod\limits_{i=1}^{m} a_i\)
这东西考虑用prufer序来理解,我们将一个连通块看做一个大的点,那么m-2个连通块在prufer序上都有一个“父亲”,但是这个父亲是一个单点,因此是\(n^{m-2}\),此外后面的大小之积相当于枚举每个连通块prufer序连出去的边是哪一条。
这显然可以用一个n^2的树形DP来完成,考虑优化
考虑上面的式子的组合意义,我们把常数因子提出来(\((z-1)^nn^{-2}\)),相当于每个连通块都有一个固定的贡献\((z-1)^{-1}n\),此外再在每个连通块中选择一个点,求总的贡献和。
这就可以用树形DP来做了,记\(F[i][0/1]\)表示当前做完了i的子树,i所在的连通块是否已经贡献过。
直接讨论相邻边是否出现即可转移,时间复杂度\(O(N)\),非常的巧妙。
问题3:
有了问题2的基础,我们来考虑问题3
不妨同样枚举重合边集的一个子集\(E_0\),m,a的定义同上
那么贡献变成了$$(z-1){n-m}\left(n{m-2}\prod a_i \right)^2$$
平方乘到指数上,依然是将常数因子提出来,把贡献分配到每个连通块上
此外由于两棵树都是生成的,因此连通块内部的边也不确定,还要乘上连通块内部的生成树个数
那么一个连通块i的贡献就是
\((z-1)^{-1}n^2a_i^2a_i^{a_i-2}=(z-1)^{-1}n^2a_i^{a_i}\)
那么上面的式子相当于连通块的带标号重复拼接
一个连通块的EGF就是$$F(x)=\sum\limits_{i>0}{(z-1){-1}n2i^i\over i!}$$
拼接以后就是\([x^n]e^{F(x)}\),乘上前面提出来的贡献就是答案了。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 100005
#define LL long long
#define mo 998244353
using namespace std;
int n,tp;
LL m;
LL ksm(LL k,LL n)
{
LL s=1;
for(;n;n>>=1,k=k*k%mo) if(n&1) s=s*k%mo;
return s;
}
namespace subtask1
{
map<int,bool> mp[N];
void solve1()
{
fo(i,1,n-1)
{
int x,y;
scanf("%d%d",&x,&y);
mp[x][y]=mp[y][x]=1;
}
int c=0;
fo(i,1,n-1)
{
int x,y;
scanf("%d%d",&x,&y);
if(mp[x][y]) c++;
}
printf("%lld\n",ksm(m,n-c));
}
}
using namespace subtask1;
namespace subtask2
{
LL f[N][2],rm,pm;
int fs[N],m1,nt[2*N],dt[2*N];
void link(int x,int y)
{
nt[++m1]=fs[x];
dt[fs[x]=m1]=y;
}
void dp(int k,int fa)
{
f[k][0]=1,f[k][1]=0;
for(int i=fs[k];i;i=nt[i])
{
int p=dt[i];
if(p!=fa)
{
dp(p,k);
f[k][1]=(f[k][1]*(f[p][0]+f[p][1])%mo+f[k][0]*f[p][1])%mo;
f[k][0]=f[k][0]*(f[p][0]+f[p][1])%mo;
}
}
f[k][1]=(f[k][1]+f[k][0]*pm%mo*(LL)n)%mo;
}
void solve2()
{
if(m==1)
{
printf("%lld\n",ksm(n,n-2));
return;
}
fo(i,1,n-1)
{
int x,y;
scanf("%d%d",&x,&y);
link(x,y),link(y,x);
}
rm=ksm(m,mo-2);
pm=ksm(rm-1,mo-2);
dp(1,0);
printf("%lld\n",f[1][1]*ksm(ksm(n,mo-2),2)%mo*ksm(rm-1,n)%mo*ksm(m,n)%mo);
}
}
using namespace subtask2;
namespace subtask3
{
#define M 262144
LL wg[M+1],wi[M+1],a[M+1],b[M+1],js[M+1],ny[M+1],ns[M+1];
int bit[M+1],cf[19],l2[M+1];
void prp(int num)
{
fo(i,0,num)
{
wi[i]=wg[i*(M/num)];
bit[i]=(bit[i>>1]>>1)|((i&1)<<(l2[num]-1));
}
}
void NTT(LL *a,bool pd,int num)
{
LL v;
fo(i,0,num-1) if(i<bit[i]) swap(a[i],a[bit[i]]);
for(int h=1,m=2,l=num>>1;m<=num;h=m,m<<=1,l>>=1)
{
int c=(!pd)?l:-l;
for(int j=0;j<num;j+=m)
{
LL *x=a+j,*y=a+h+j,*w=(!pd)?wi:wi+num;
fo(i,0,h-1)
{
v=*y * *w%mo;
*y=(*x-v+mo)%mo,*x=(*x+v)%mo;
x++,y++,w+=c;
}
}
}
if(pd) fo(i,0,num-1) a[i]=a[i]*ny[num]%mo;
}
void getinv(int n,LL *a,LL *b)
{
static LL u1[M+1],u2[M+1];
fo(i,0,cf[l2[n]]-1) b[i]=0;
b[0]=ksm(a[0],mo-2);
for(int m=1,t=2,num=4;m<n;m=t,t=num,num<<=1)
{
prp(num);
fo(i,0,num-1) u1[i]=u2[i]=0;
fo(i,0,m-1) u1[i]=b[i];
fo(i,0,t-1) u2[i]=a[i];
NTT(u1,0,num),NTT(u2,0,num);
fo(i,0,num-1) u1[i]=u1[i]*u1[i]%mo*u2[i]%mo;
NTT(u1,1,num);
fo(i,0,t-1) b[i]=((LL)2*b[i]-u1[i]+mo)%mo;
}
}
void getln(int n,LL *a,LL *b)
{
static LL u1[M+1],u2[M+1];
int num=cf[l2[2*n+1]];
getinv(n+1,a,u1);
fo(i,n+1,num-1) u1[i]=0;
prp(num);
fo(i,0,n-1) u2[i]=a[i+1]*(LL)(i+1)%mo;
fo(i,n,num-1) u2[i]=0;
NTT(u1,0,num),NTT(u2,0,num);
fo(i,0,num-1) u1[i]=u1[i]*u2[i]%mo;
NTT(u1,1,num);
b[0]=0;
fo(i,1,n) b[i]=u1[i-1]*ny[i]%mo;
}
void getexp(int n,LL *a,LL *b)
{
static LL u1[M+1],u2[M+1];
b[0]=1;
fo(i,1,cf[l2[n]]) b[i]=0;
for(int m=1,t=2,num=4;m<=n;m=t,t=num,num<<=1)
{
getln(t-1,b,u1);
fo(i,0,t-1) u1[i]=(-u1[i]+a[i]+mo+mo)%mo;
fo(i,t,num-1) u1[i]=0;
u1[0]++;
fo(i,0,m-1) u2[i]=b[i];
fo(i,m,num-1) u2[i]=0;
prp(num);
NTT(u1,0,num),NTT(u2,0,num);
fo(i,0,num-1) u1[i]=u1[i]*u2[i]%mo;
NTT(u1,1,num);
fo(i,0,t-1) b[i]=u1[i];
}
}
void solve3()
{
if(m==1) {printf("%lld\n",ksm(n,2*n-4));return;}
wg[0]=1,wg[1]=ksm(3,(mo-1)/M);
fo(i,2,M) wg[i]=wg[i-1]*wg[1]%mo;
fo(i,0,18) l2[cf[i]=1<<i]=i;
fod(i,M-1,2) if(!l2[i]) l2[i]=l2[i+1];
js[0]=ns[0]=js[1]=ns[1]=ny[1]=1;
fo(i,2,M) js[i]=js[i-1]*(LL)i%mo,ny[i]=(-ny[mo%i]*(LL)(mo/i)%mo+mo)%mo;
fo(i,2,M) ns[i]=ns[i-1]*ny[i]%mo;
LL rm=ksm(m,mo-2),pm=ksm(rm-1,mo-2);
fo(i,1,n) a[i]=pm*(LL)n%mo*(LL)n%mo*ksm(i,i)%mo*ns[i]%mo;
getexp(n,a,b);
printf("%lld\n",b[n]*ksm(ksm(n,mo-2),4)%mo*ksm(rm-1,n)%mo*ksm(m,n)%mo*js[n]%mo);
}
}
using namespace subtask3;
int main()
{
cin>>n>>m>>tp;
if(tp==0) solve1();
else if(tp==1) solve2();
else solve3();
}
[LibreOJ #2983]【WC2019】数树【计数】【DP】【多项式】的更多相关文章
- 洛谷 P5206: bzoj 5475: LOJ 2983: [WC2019] 数树
一道技巧性非常强的计数题,历年WC出得最好(同时可能是比较简单)的题目之一. 题目传送门:洛谷P5206. 题意简述: 给定 \(n, y\). 一张图有 \(|V| = n\) 个点.对于两棵树 \ ...
- 【WC2019】数树 树形DP 多项式exp
题目大意 有两棵 \(n\) 个点的树 \(T_1\) 和 \(T_2\). 你要给每个点一个权值吗,要求每个点的权值为 \([1,y]\) 内的整数. 对于一条同时出现在两棵树上的边,这条边的两个端 ...
- [WC2019] 数树
[WC2019] 数树 Zhang_RQ题解(本篇仅概述) 前言 有进步,只做了半天.... 一道具有极强综合性的数数好题! 强大的多合一题目 精确地数学推导和耐心. 有套路又不失心意. 融合了: 算 ...
- 并不对劲的bzoj5475:loj2983:p5206:[wc2019]数树
题目大意 task0:有两棵\(n\)(n\leq10^5)个点的树\(T1,T2\),每个点的点权可以是一个在\([1,y]\)里的数,如果两个点既在\(T1\)中有直接连边,又在\(T2\)中有直 ...
- BZOJ5475 WC2019数树(prufer+容斥原理+树形dp+多项式exp)
因为一大堆式子实在懒得写题解了.首先用prufer推出CF917D用到的结论,然后具体见前言不搭后语的注释. #include<iostream> #include<cstdio&g ...
- 洛谷P5206 [WC2019]数树 [容斥,DP,生成函数,NTT]
传送门 Orz神仙题,让我长了许多见识. 长式子警告 思路 y=1 由于y=1时会导致后面一些式子未定义,先抓出来. printf("%lld",opt==0?1:(opt==1? ...
- [LOJ2983] [WC2019] 数树
题目链接 LOJ:https://loj.ac/problem/2983 BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=5475 洛谷:https ...
- 洛谷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 ...
随机推荐
- 8.3 mysql 表操作
库操作 一 系统数据库 information_schema: 虚拟库,不占用磁盘空间,存储的是数据库启动后的一些参数,如用户表信息.列信息.权限信息.字符信息等 performance_sch ...
- windows平台使用spark-submit以client方式提交spark应用到standalone集群
1.spark应用打包,我喜欢打带依赖的,这样省事. 2.使用spark-submit.bat 提交应用,代码如下: for /f "tokens=1,2 delims==" %% ...
- (二分搜索 )Strange fuction -- HDU -- 2899
链接: http://acm.hdu.edu.cn/showproblem.php?pid=2899 Time Limit: 2000/1000 MS (Java/Others) Memory ...
- ASP.NET Core2基于RabbitMQ对Web前端实现推送功能
在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...
- Windows 以及 Xcode下编译调试 libcurl 源码
curl 这个工具大家都很熟悉. 前几天因为要跟踪curl的实现细节, 不得不设法搭建curl的调试工程. 我们分别在windows visual studio 和 mac 上的 xcode 下搭建调 ...
- Gluster 常用命令
Gluster 常用命令1 服务器节点# gluster peer status //查看所有节点信息,显示时不包括本节点 # gluster peer probe NODE-NAME //添加节点 ...
- excel中如何让每n行显示同一个数据
由于需要将数据按照下表格式存储,以方便读取展示,年份列需要每隔7行再递增1 方法: 1. 输入这个公式: = INT((ROW(E1)-1)/ 5)+ 1 进入一个空白单元格,您可以在其中填写序列号, ...
- asp:Repeater数据源为空时处理方式
当Repeater的数据源为空时,会显示空白,界面不友好,以下方式可以简单处理. <asp:repeater runat="server" id="rplist2& ...
- WPF 组织机构下拉树多选,递归绑定方式现实
使用HierarchicalDataTemplate递归绑定现实 XAML代码: <UserControl x:Class="SunCreate.CombatPlatform.Clie ...
- Amazon新一代云端关系数据库Aurora(下)
本文由 网易云发布. 作者:郭忆 本篇文章仅限内部分享,如需转载,请联系网易获取授权. 故障恢复 MySQL基于Check point的机制,周期性的建立redo log与数据页的一致点.一旦数据库 ...