BZOJ 3456 城市规划 (组合计数、DP、FFT)

2024-02-15 15:48

本文主要是介绍BZOJ 3456 城市规划 (组合计数、DP、FFT),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目链接: 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)\), 于是上式可以改写为\[[x^n]G(x)=[x^n]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[x^n]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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711825

相关文章

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4869(逆元+求组合数)

//输入n,m,n表示翻牌的次数,m表示牌的数目,求经过n次操作后共有几种状态#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#includ

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs

uva 10069 DP + 大数加法

代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>#include <cl

uva 10029 HASH + DP

题意: 给一个字典,里面有好多单词。单词可以由增加、删除、变换,变成另一个单词,问能变换的最长单词长度。 解析: HASH+dp 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

XTU 1233 n个硬币连续m个正面个数(dp)

题面: Coins Problem Description: Duoxida buys a bottle of MaiDong from a vending machine and the machine give her n coins back. She places them in a line randomly showing head face or tail face o