zoukankan      html  css  js  c++  java
  • JZOJ 3303. 【集训队互测2013】城市规划(卷积+分治NTT)

    JZOJ 3303. 【集训队互测2013】城市规划

    题目

    Description

    刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了.
    刚才说过, 阿狸的国家有n 个城市, 现在国家需要在某些城市对之间建立一些贸易路线, 使得整个国家的任意两个城市都直接或间接的连通.
    为了省钱, 每两个城市之间最多只能有一条直接的贸易路径. 对于两个建立路线的方案, 如果存在一个城市对, 在两个方案中是否建立路线不一样, 那么这两个方案就是不同的, 否则就是相同的. 现在你需要求出一共有多少不同的方案.
    好了, 这就是困扰阿狸的问题. 换句话说, 你需要求出n 个点的简单(无重边无自环)无向连通图数目.
    由于这个数字可能非常大, 你只需要输出方案数mod 1004535809(479 * 2 ^21 + 1)即可.

    Input

    仅一行一个整数n(<=130000)

    Output

    仅一行一个整数, 为方案数mod 1004535809.

    Sample Input

    输入1:
    3

    输入2:
    4

    输入3:
    100000

    Sample Output

    输出1:
    4

    输出2:
    38

    输出3:
    829847355

    Data Constraint

    对于20%的数据, n <= 10
    对于40%的数据, n <= 1000
    对于60%的数据, n <= 30000
    对于80%的数据, n <= 60000
    对于100%的数据, n <= 130000

    题解

    • 这种计数题,按照常规的思路,先考虑一下递推,
    • i i i的答案为 f i f_i fi,那么 f i f_i fi可以由 f j ( 1 ≤ j < i ) f_j(1≤j<i) fj(1j<i)怎么转移,
    • 经过思考后,发现好像不是很可行。
    • (也许可以,至少多数人都没有推出来)
    • 那我们想想容斥(不要怕),用总方案数减去不可行的方案数(这就是容斥的全部),
    • 开始推一波式子:
    • f i = 2 C i 2 − ∑ j = 1 i − 1 f j ∗ C i − 1 j − 1 ∗ 2 C i − j 2 f_i=2^{C_i^2}-sum_{j=1}^{i-1}f_j*C_{i-1}^{j-1}*2^{C_{i-j}^2} fi=2Ci2j=1i1fjCi1j12Cij2
    • 2 C i 2 2^{C_i^2} 2Ci2 i i i个点之间所有无向边选或不选的总方案数;
    • ∑ j = 1 i − 1 sum_{j=1}^{i-1} j=1i1是枚举任意一个点(比如说 1 1 1号点)所在的连通块的大小;
    • f j f_j fj表示该连通块组成的方案数(当点已经确定的情况下);
    • C i − 1 j − 1 C_{i-1}^{j-1} Ci1j1是在剩余点中选出与 i i i组成大小为 j j j的连通块的方案数;
    • 2 C i − j 2 2_{C_{i-j}^2} 2Cij2是该连通块以外的点之间所有边选或不选的方案数;
    • 这样可以保证不重不漏!!!
    • g i g_i gi表示 2 C i 2 2^{C_i^2} 2Ci2,也就是 i i i个点之间所有无向边选或不选的总方案数,
    • f i = 2 C i 2 − ∑ j = 1 i − 1 f j ∗ C i − 1 j − 1 ∗ 2 C i − j 2 f_i=2^{C_i^2}-sum_{j=1}^{i-1}f_j*C_{i-1}^{j-1}*2^{C_{i-j}^2} fi=2Ci2j=1i1fjCi1j12Cij2
    • = g i − ∑ j = 1 i − 1 f j ∗ C i − 1 j − 1 ∗ g i − j =g_i-sum_{j=1}^{i-1}f_j*C_{i-1}^{j-1}*g_{i-j} =gij=1i1fjCi1j1gij
    • = g i − ∑ j = 1 i − 1 f j ∗ ( i − 1 ) ! ( j − 1 ) ! ( i − j ) ! ∗ g i − j =g_i-sum_{j=1}^{i-1}f_j*frac{(i-1)!}{(j-1)!(i-j)!}*g_{i-j} =gij=1i1fj(j1)!(ij)!(i1)!gij
    • = g i − ( i − 1 ) ! ∑ j = 1 i − 1 f j ( j − 1 ) ! ∗ g i − j ( i − j ) ! =g_i-(i-1)!sum_{j=1}^{i-1}frac{f_j}{(j-1)!}*frac{g_{i-j}}{(i-j)!} =gi(i1)!j=1i1(j1)!fj(ij)!gij
    • 右边显然是个卷积的形式,用 N T T NTT NTT来做,
    • 但是要做 n n n次,那么用分治 N T T NTT NTT.

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define md 1004535809
    #define LL long long 
    #define N 300010
    LL f[N],g[N],q[N],ny[N],ws[N],wt[N],nn[N];
    LL a[N],b[N],rev[N];
    LL ksm(LL x,LL y)
    {
    	if(!y) return 1;
    	LL l=ksm(x,y/2);
    	if(y%2) return l*l%md*x%md;
    	return l*l%md;
    }
    void NTT(LL *a,int ln,int p)
    {
    	for(int i=0;i<ln;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
    	for(int i=1;i<ln;i*=2)
    	{
    		LL w=ws[i*2];
    		if(p==-1) w=wt[i*2];
    		for(int j=0;j<ln;j+=2*i)
    		{
    			LL w0=1;
    			for(int k=0;k<i;k++)
    			{
    				LL A=a[k+j],B=w0*a[k+j+i];
    				a[k+j]=(A+B%md)%md;
    				a[k+j+i]=(A-B%md+md)%md;
    				w0=w0*w%md;
    			}
    		}
    	}
    	if(p==-1)
    	{
    		for(LL i=0;i<ln;i++) a[i]=a[i]*nn[ln]%md;
    	}
    }
    void cdq(LL l,LL r)
    {
    	if(l==r)
    	{
    		f[l]=(g[l]-f[l]*q[l-1]%md+md)%md;
    	}
    	else
    	{
    		int mid=(l+r)/2,t=0,s=1,i;
    		cdq(l,mid);
    		for(;s<(r-l+1);s*=2) t++;
    		s*=2;
    		rev[0]=0;
    		for(i=1;i<s;i++) rev[i]=rev[i/2]/2|((i&1)<<t);
    		for(i=0;i<=mid-l;i++) a[i]=f[i+l]*ny[i+l-1]%md;
    		for(i=mid-l+1;i<s;i++) a[i]=0;
    		for(i=1;i<=r-l;i++) b[i]=g[i]*ny[i]%md;
    		b[0]=0;
    		for(i=r-l+1;i<s;i++) b[i]=0;
    		NTT(a,s,1),NTT(b,s,1);
    		for(i=0;i<s;i++) a[i]=a[i]*b[i]%md;
    		NTT(a,s,-1);
    		for(i=1;i<=mid-l+1;i++) f[i+mid]=(f[i+mid]+a[i+mid-l])%md;
    		cdq(mid+1,r);
    	}
    }
    int main()
    {
    	LL n,i,j;
    	scanf("%lld",&n);
    	LL ln=1;
    	for(;ln<n;ln*=2);
    	q[0]=1,ny[0]=1;
    	for(i=1;i<=ln*2;i++) q[i]=q[i-1]*i%md;
    	ny[ln*2]=ksm(q[ln*2],md-2);
    	for(i=ln*2-1;i;i--) ny[i]=ny[i+1]*(i+1)%md;
    	for(i=1;i<=ln*2;i++) g[i]=ksm(2,1ll*i*(i-1)/2);
    	for(i=1;i<=ln*2;i++) nn[i]=ksm(i,md-2);
    	for(i=1;i<=ln*2;i*=2) ws[i]=ksm(3,(md-1)/i),wt[i]=ksm(ws[i],md-2);
    	cdq(1,ln);
    	printf("%lld",f[n]);
    	return 0;
    }
    
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    杜教筛刷题总结
    后缀自动机刷题总结
    回文自动机刷题总结
    后缀数组刷题总结
    LCT刷题总结
    省选模拟一题解
    FFT/NTT中档题总结
    二项式反演总结
    JS只能输入数字,数字和字母等的正则表达式
    jquery 条件搜索某个标签下的子标签
  • 原文地址:https://www.cnblogs.com/LZA119/p/13910073.html
Copyright © 2011-2022 走看看