把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ3456】城市规划(多项式求逆+NTT)

点此看题面

大致题意: 求出\(n\)个点的简单(无重边无自环)有标号无向连通图数目。

前言

在我的想象里,这道题应该是无比冗长、十分复杂、令人懵逼的题面+极其恐怖、难以理解、又臭又长的式子+分治\(NTT\)+多项式求逆+多项式对数+指数型生成函数+一堆五花八门、乱七八糟的东西

然而真正点开这道题,题面简洁明了(就一句话),题解里式子推出来我居然也能看得懂,代码里也没啥乱七八糟的东西,就一个多项式求逆+\(NTT\)

或许是我看的题解比较良心?

推式子

这是一个常见的套路。(我也不知道常见不常见,反正我似乎曾遇到过,还被闪指导指导过)

我们设\(f(x)\)\(x\)个点的无向连通图数目(即答案),\(g(x)\)\(x\)个点的图的数目

对于\(g(x)\),我们只要考虑共计\(\frac{x(x-1)}2\)条边中每条边是否连在图上,即:

\[g(x)=2^{\frac{x(x-1)}2} \]

然后我们就要想办法把这个式子变复杂\(f(x)\)\(g(x)\)扯上关系。

如果我们对于\(1\)号点,去枚举它所在的连通块大小,那么就有:

\[g(x)=\sum_{i=1}^xC_{x-1}^{i-1}f(i)g(x-i) \]

即,从剩余的共\(n-1\)个节点选出\(i-1\)个点与\(1\)号点拼成连通块,而其他的点可以随意。

接下来这一步我可以确信是一个常见套路,就是拆了组合数(此处我们顺便代入\(g(x)\)的值),得到:

\[2^{\frac{x(x-1)}2}=\sum_{i=1}^x\frac{(x-1)!}{(i-1)!(x-i)!}f(i)2^{\frac{(x-i)(x-i-1)}2} \]

移项,得到:

\[\frac{2^{\frac{x(x-1)}2}}{(x-1)!}=\sum_{i=1}^x\frac{f(i)}{(i-1)!}\cdot\frac{2^{\frac{(x-i)(x-i-1)}2}}{(x-i)!} \]

然后我们就发现每一项都有着自己对应的下标,即设(其中\(f(x)[i]\)表示函数\(f(x)\)\(i\)次项系数):

\[F(x)[i]=\frac{f(i)}{(i-1)!}(i\ge 1) \]

\[G(x)[i]=\frac{2^{\frac{i(i-1)}2}}{i!}(i\ge 0) \]

\[H(x)[i]=\frac{2^{\frac{i(i-1)}2}}{(i-1)!}(i\ge 1) \]

则我们发现原式相当于:(此处用\(n\)替换上式的\(x\),以免定义重复)

\[H(x)[n]=\sum_{i=1}^nF(x)[i]\cdot G(x)[n-i] \]

显然这就是一个卷积的形式,得到:

\[H=F*G \]

那么就有:

\[F=H*G^{-1} \]

因此直接先预处理出\(G,H\),然后多项式求逆+\(NTT\)求出\(F\),则\(f(n)=F(x)[n]\times(n-1)!\),这样一来就搞定了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 130000
#define X 1004535809
#define QI(x) QP(x,X-2)
using namespace std;
int n,g[N<<2],g_[N<<2],h[N<<2],Fac[N+5],IFac[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
class Poly//多项式模板
{
	private:
		#define PR 3
		#define IPR 334845270
		int P,L,R[N<<2],t[N<<2];
		I void T(int *s,CI p)
		{
			RI i,j,k,U,S,x,y;for(i=0;i^P;++i) i<R[i]&&(s[i]^=s[R[i]]^=s[i]^=s[R[i]]);
			for(i=1;i^P;i<<=1) for(U=QP(p,(X-1)/(i<<1)),j=0;j^P;j+=i<<1) for(S=1,k=0;k^i;
				++k,S=1LL*S*U%X) s[j+k]=((x=s[j+k])+(y=1LL*S*s[i+j+k]%X))%X,s[i+j+k]=(x-y+X)%X;
		}
	public:
		I void Inv(CI n,int *a,int *b)//多项式求逆
		{
			if(!n) return (void)(b[0]=QI(a[0]));Inv(n>>1,a,b);RI i;
			P=1,L=0;W(P<=(n<<1)) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
			for(i=0;i<=n;++i) t[i]=a[i];T(b,PR),T(t,PR);
			for(i=0;i^P;++i) b[i]=(2LL*b[i]-1LL*t[i]*b[i]%X*b[i]%X+X)%X;T(b,IPR);
			RI t=QI(P);for(i=0;i<=n;++i) b[i]=1LL*b[i]*t%X;for(;i^P;++i) b[i]=0;
		}
		I int NTT(CI n,int *a,int *b)//NTT,只用求出第n项系数
		{
			RI i;P=1,L=0;W(P<=(n<<1)) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);
			for(T(a,PR),T(b,PR),i=0;i^P;++i) a[i]=1LL*a[i]*b[i]%X;return T(a,IPR),1LL*a[n]*QI(P)%X;
		}
}P;
int main()
{
	RI i;scanf("%d",&n);for(Fac[0]=i=1;i<=n;++i) Fac[i]=1LL*Fac[i-1]*i%X;
	for(IFac[n]=QI(Fac[n]),i=n-1;~i;--i) IFac[i]=1LL*IFac[i+1]*(i+1)%X;
	for(i=0;i<=n;++i) g[i]=1LL*QP(2,(1LL*i*(i-1)/2)%(X-1))*IFac[i]%X;//预处理g
	for(i=1;i<=n;++i) h[i]=1LL*QP(2,(1LL*i*(i-1)/2)%(X-1))*IFac[i-1]%X;//预处理h
	return P.Inv(n,g,g_),printf("%d",1LL*P.NTT(n,g_,h)*Fac[n-1]%X),0;//多项式求逆后NTT
}
posted @ 2020-05-22 10:43  TheLostWeak  阅读(191)  评论(0编辑  收藏  举报