• 【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}^xfrac{(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}^xfrac{f(i)}{(i-1)!}cdotfrac{2^{frac{(x-i)(x-i-1)}2}}{(x-i)!} ]

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

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

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

    [H(x)[i]=frac{2^{frac{i(i-1)}2}}{(i-1)!}(ige 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] imes(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
    }
    
  • 相关阅读:
    函数模板、函数模板特化、重载函数模板、非模板函数重载
    输出流格式化(以操纵子方式格式化,以ios类成员函数方式格式化)
    文件的读写、二进制文件的读写、文件随机读写
    文件流(fstream, ifstream, ofstream)的打开关闭、流状态
    流类库继承体系(IO流,文件流,串流)和 字符串流的基本操作
    对象语义与值语义、资源管理(RAII、资源所有权)、模拟实现auto_ptr<class>、实现Ptr_vector
    operator new 和 operator delete 实现一个简单内存泄漏跟踪器
    异常与继承、异常与指针、异常规格说明
    程序错误、异常(语法、抛出、捕获、传播)、栈展开
    C语言错误处理方法、C++异常处理方法(throw, try, catch)简介
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3456.html
Copyright © 2020-2023  润新知