题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3456

著名的多项式练习题,做法也很多,终于切掉了纪念

首先求一波递推式: 令\(F(n)\)为\(n\)个点的有标号无向连通图的个数,则考虑补集转化为有标号无向不连通图的个数,然后枚举\(1\)号点所在联通块的大小: $$F(n)=2^{n\choose 2}-\sum^{n-1}_{i=1} {n-1\choose i-1} F(i)2^{n-i\choose 2}$$

这样可以做到\(O(n^2)\), 后面就该大佬们各显神通了,我在这里整理一下四种做法:

做法一

直接使用分治NTT优化,时间复杂度\(O(n\log^2n)\)。但我不会分治NTT,所以不具体说了。

做法二

\[F(n)=2^{n\choose 2}-\sum^{n-1}_{i=1}\frac{(n-1)!}{(i-1)!(n-i)!}F(i)2^{n-i\choose 2}\\
\frac{F(n)}{(n-1)!}=\frac{2^{n\choose 2}}{(n-1)!}-\sum^{n-1}_{i=1} \frac{F(i)}{(i-1)!}\frac{2^{n-i\choose 2}}{(n-i)!}$$移项可得$$\frac{2^{n\choose 2}}{(n-1)!}=\sum^{n}_{i=1} \frac{F(i)}{(i-1)!}\frac{2^{n-i\choose 2}}{(n-i)!}\]

令\(A(x)=\sum_{n>0}\frac{F(n)}{(n-1)!}, G(x)=\sum_{n\ge 0}\frac{2^{n\choose 2}}{n!}, H(x)=\sum_{n>0}\frac{2^{n\choose 2}}{(n-1)!}\), 则有$$F(x)G(x)=H(x)\ F(x)=H(x)G(x)^{-1}$$

多项式求逆即可。

时间复杂度\(O(n\log n)\).

这应该是代码复杂度和运行效率上最好的一种做法,但是做法三和做法四也有一定的启发意义。

做法三

设\(G(n)=2^{n\choose 2}\)表示\(n\)个点有标号无向图的个数。设\(F(x),G(x)\)分别为\(F(n),G(n)\)的指数生成函数(EGF).

由于一个有标号无向图由若干个彼此之间无顺序的联通块组成,因此其指数生成函数\(G(x)=\sum_{n\ge 1}\frac{F(x)^n}{n!}\).

即\(G(x)=e^{F(x)}\), \(F(x)=\ln G(x)\). 多项式\(\ln\)即可。

时间复杂度\(O(n\log n)\).

做法四

(这个做法是我自己想的,有错敬请指出)(这种做法其实是用另一种方式推导做法三)

感谢_rqy大爷博客里的生成函数简介。

仿照求Bell数的EGF方法,进行以下推导: $$\frac{F(n)}{n!}=\frac{G(n)}{n!}-\sum^{n-1}{i=1} \frac{F(i)}{n(i-1)!}\frac{G(n-i)}{(n-i)!}\ \frac{G(n)}{n!}=\frac{F(n)}{n!}+\frac{1}{n}\sum^{n-1}{i=1}\frac{iF(i)}{i!}\frac{G(n-i)}{(n-i)!}$$

这里我们发现\(\frac{iF(i)}{i!}\)就是\([x^{i-1}]F'(x)\), 于是上式可以改写为$$[xn]G(x)=[xn]F(x)+\frac{1}{n}\sum{n-1}_{i=1}[x{i-1}]F'(x)\times [x^{n-i}]G(x)\ =\frac{1}{n}(n[xn]F(x)+\sum{n-1}_{i=1}[x^{i-1}]F'(x)\times [x^{n-i}]G(x))\ =\frac{1}{n}\sum{n}_{i=1}[x{i-1}]F'(x)\times [x^{n-i}]G(x)\ G(x)=\int^x_0 F'(x)G(x)\text{d}x\ \frac{G'(x)}{G(x)}=F'(x)\ \ln G(x)=F(x)$$.

一定要注意求和边界! 我推式子的时候没注意求和上界是\(n\)还是\((n-1)\)的问题结果一直推出来\(G(x)=F(x)+\int^x_0 F'(x)G(x)\text{d}x\)查了一小时……

代码

做法二

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iostream>
#define llong long long
using namespace std; inline int read()
{
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
if(f) return x;
return -x;
} const int N = 1<<19;
const int LGN = 19;
const int P = 1004535809;
const int G = 3;
llong fact[N+3],finv[N+3]; llong quickpow(llong x,llong y)
{
llong cur = x,ret = 1ll;
for(int i=0; y; i++)
{
if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
cur = cur*cur%P;
}
return ret;
}
llong mulinv(llong x) {quickpow(x,P-2);} namespace Polynomial
{
llong tmp1[N+3],tmp2[N+3],tmp3[N+3],tmp4[N+3],tmp5[N+3],tmp6[N+3];
int fftid[N+3];
int getdgr(int n)
{
int ret = 1; while(ret<=n) ret<<=1;
return ret;
}
void init_fftid(int dgr)
{
int len = 0; for(int i=1; i<=LGN; i++) {if((1<<i)==dgr) {len = i; break;}}
for(int i=1; i<dgr; i++) fftid[i] = (fftid[i>>1]>>1)|((i&1)<<(len-1));
}
void ntt(int dgr,int coe,llong poly[],llong ret[])
{
init_fftid(dgr);
if(poly==ret) {for(int i=0; i<dgr; i++) {if(i<fftid[i]) swap(ret[i],ret[fftid[i]]);}}
else {for(int i=0; i<dgr; i++) ret[i] = poly[fftid[i]];}
for(int i=1; i<=(dgr>>1); i<<=1)
{
llong tmp = quickpow(G,(P-1)/(i<<1));
if(coe==-1) tmp = mulinv(tmp);
for(int j=0; j<dgr; j+=(i<<1))
{
llong expn = 1ll;
for(int k=0; k<i; k++)
{
llong x = ret[j+k],y = ret[i+j+k]*expn%P;
ret[j+k] = (x+y)%P;
ret[j+i+k] = (x-y+P)%P;
expn = expn*tmp%P;
}
}
}
if(coe==-1)
{
llong tmp = mulinv(dgr);
for(int i=0; i<dgr; i++) ret[i] = ret[i]*tmp%P;
}
}
void polymul(int dgr,llong poly1[],llong poly2[],llong ret[])
{
ntt(dgr<<1,1,poly1,tmp1); ntt(dgr<<1,1,poly2,tmp2);
for(int i=0; i<(dgr<<1); i++) tmp2[i] = tmp1[i]*tmp2[i]%P;
ntt(dgr<<1,-1,tmp2,ret);
}
void polyinv(int dgr,llong poly[],llong ret[])
{
for(int i=0; i<dgr; i++) ret[i] = tmp1[i] = 0ll;
ret[0] = mulinv(poly[0]); tmp1[0] = poly[0];
for(int i=1; i<=(dgr>>1); i<<=1)
{
for(int j=i; j<(i<<1); j++) tmp1[j] = poly[j];
ntt((i<<2),1,ret,tmp2); ntt((i<<2),1,tmp1,tmp3);
for(int j=0; j<(i<<2); j++) tmp2[j] = tmp2[j]*tmp2[j]%P*tmp3[j]%P;
ntt((i<<2),-1,tmp2,tmp3);
for(int j=0; j<(i<<1); j++) ret[j] = (2ll*ret[j]-tmp3[j]+P)%P;
}
}
}
llong f[N+3],g[N+3],h[N+3],ginv[N+3];
int n; int main()
{
fact[0] = 1ll; for(int i=1; i<=N; i++) fact[i] = fact[i-1]*i%P;
finv[N] = quickpow(fact[N],P-2); for(int i=N-1; i>=0; i--) finv[i] = finv[i+1]*(i+1)%P;
scanf("%d",&n); int dgr = Polynomial::getdgr(n);
for(int i=0; i<=n; i++) {g[i] = quickpow(2ll,i*(i-1ll)/2ll)*finv[i]%P;}
for(int i=1; i<=n; i++) {h[i] = quickpow(2ll,i*(i-1ll)/2ll)*finv[i-1]%P;}
// printf("g: "); for(int i=0; i<dgr; i++) printf("%lld ",g[i]); puts("");
// printf("h: "); for(int i=0; i<dgr; i++) printf("%lld ",h[i]); puts("");
Polynomial::polyinv(dgr,g,ginv);
// printf("ginv: "); for(int i=0; i<dgr; i++) printf("%lld ",ginv[i]); puts("");
Polynomial::polymul(dgr,ginv,h,f);
printf("%lld\n",f[n]*fact[n-1]%P);
return 0;
}

生成函数这东西真的是有趣!!!

BZOJ 3456 城市规划 (组合计数、DP、FFT)的更多相关文章

  1. [BZOJ 3456]城市规划(cdq分治+FFT)

    [BZOJ 3456]城市规划(cdq分治+FFT) 题面 求有标号n个点无向连通图数目. 分析 设\(f(i)\)表示\(i\)个点组成的无向连通图数量,\(g(i)\)表示\(i\)个点的图的数量 ...

  2. [ZJOI2010]排列计数 (组合计数/dp)

    [ZJOI2010]排列计数 题目描述 称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有 ...

  3. [BZOJ 4332] [JSOI2012]分零食(DP+FFT)

    [BZOJ 4332] [JSOI2012]分零食(DP+FFT) 题面 同学们依次排成了一列,其中有A位小朋友,有三个共同的欢乐系数O,S和U.如果有一位小朋友得到了x个糖果,那么她的欢乐程度就是\ ...

  4. BZOJ 3456: 城市规划 [多项式求逆元 组合数学 | 生成函数 多项式求ln]

    3456: 城市规划 题意:n个点组成的无向连通图个数 以前做过,今天复习一下 令\(f[n]\)为n个点的无向连通图个数 n个点的完全图个数为\(2^{\binom{n}{2}}\) 和Bell数的 ...

  5. bzoj 1004 Cards 组合计数

    这道题考察的是组合计数(用Burnside,当然也可以认为是Polya的变形,毕竟Polya是Burnside推导出来的). 这一类问题的本质是计算置换群(A,P)中不动点个数!(所谓不动点,是一个二 ...

  6. BZOJ1079 [SCOI2008]着色方案[组合计数DP]

    $有a_{1}个1,a_{2}个2,...,a_{n}个n(n<=15,a_{n}<=5),求排成一列相邻位不相同的方案数.$ 关于这题的教训记录: 学会对于复杂的影响分开计,善于发现整体 ...

  7. bzoj 3456 城市规划 —— 分治FFT / 多项式求逆 / 指数型生成函数(多项式求ln)

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 首先考虑DP做法,正难则反,考虑所有情况减去不连通的情况: 而不连通的情况就是那个经典 ...

  8. BZOJ 3456: 城市规划 与 多项式求逆算法介绍(多项式求逆, dp)

    题面 求有 \(n\) 个点的无向有标号连通图个数 . \((1 \le n \le 1.3 * 10^5)\) 题解 首先考虑 dp ... 直接算可行的方案数 , 容易算重复 . 我们用总方案数减 ...

  9. bzoj 3456 城市规划——分治FFT / 多项式求逆 / 多项式求ln

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3456 分治FFT: 设 dp[ i ] 表示 i 个点时连通的方案数. 考虑算补集:连通的方 ...

随机推荐

  1. MYSQL中的UNION和UNION ALL

    SQL UNION 操作符 UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每 ...

  2. Spring 自定义注解,结合AOP,配置简单日志注解 (转)

    java在jdk1.5中引入了注解,spring框架也正好把java注解发挥得淋漓尽致. 下面会讲解Spring中自定义注解的简单流程,其中会涉及到spring框架中的AOP(面向切面编程)相关概念. ...

  3. docker-文件系统出错处理

    Error response from daemon: error creating overlay mount to /var/lib/docker/overlay2 2018年03月17日 22: ...

  4. Max History CodeForces - 938E (组合计数)

    You are given an array a of length n. We define fa the following way: Initially fa = 0, M = 1; for e ...

  5. int 问号的使用

    单问号---为泛型 Nullable<int> 的简写方式. 双问号---用于判断前一个操作数是否为null,如为null则"返回"后一个操作数,否则"返回& ...

  6. C语言typedef详解

    原文链接 C语言允许用户使用 typedef 关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称.数组类型名称.指针类型名称与用户自定义的结构型名称.共用型名称.枚举型名称等.一旦用户在 ...

  7. 请写出正则表达式(regex),取得下列黄色部分的字符串 TEL: 02-236-9655/9659 FAX:02-236-9654 (黄色部分即02-236-9655/9659 ) ( 测试面试题)

    请写出正则表达式(regex),取得下列黄色部分的字符串 TEL: 02-236-9655/9659 FAX:02-236-9654 答: package test1; import java.uti ...

  8. 理解js异步编程

    Promise 背景 javascript语言的一大特点就是单线程,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码,也就是说,同一个时间只能做一件事. 怎么做到异步编程?回调函数.直到no ...

  9. Linux使用Docker启动Elasticsearch并配合Kibana使用,安装ik分词器

    注意事项 这里我的Linux虚拟机的IP地址是192.168.1.3 Docker运行Elasticsearch容器之后不会立即有反应,要等一会,等待容器内部启动Elasticsearch,才可以访问 ...

  10. ansible简要说明

    说明 Ansible是一个python编写模型驱动的配置管理器,支持多节点发布.远程任务执行.默认使用 SSH 进行远程连接.无需在被管理节点上安装附加软件,可使用各种编程语言进行扩展.本文基于ans ...