Tibbar的后花园

24 篇文章 0 订阅
7 篇文章 0 订阅

一、题目

Input:
给出 n个点(n个点是互异的),求出满足下列条件的连边方案:

  • 不存在重边和自环。
  • 不存在三个点a,b,c使三个点间两两距离相等。

Output:
一个整数n,表示方案数对 1004535809 1004535809 1004535809 479 ∗ 2 21 + 1 479*2^{21}+1 479221+1,质数)取模的结果。
n ≤ 1 0 6 n\leq 10^6 n106

二、解法

本题最重要的条件是:不存在三个点a,b,c使三个点间两两距离相等,这告诉我们图中不能存在度数 ≤ 3 \leq 3 3的点,同样也不能存在长度为 3 3 3的倍数的环,所以图中就只能有链和长度不为 3 3 3的环。
我们分别考虑它们的指数型生成函数(EGF),为什么要用EGF呢?是因为 n n n个点互异,在链和环的内部我们会考虑顺序,所以要除去内部的排列,但在外面我们是需要做一个全排列来确定顺序,问题就很像多重集的排列,所以我们使用EGF。长度为 i i i的链有 i ! 2 \frac{i!}{2} 2i!中,环有 i ! 2 i \frac{i!}{2i} 2ii!种,所以它们的生成函数长这样:
A ( x ) = ∑ i = 2 n i ! 2 x i i ! = ∑ i = 2 n x i 2 A(x)=\sum_{i=2}^{n} \frac{i!}{2}\frac{x^i}{i!}=\sum_{i=2}^n \frac{x^i}{2} A(x)=i=2n2i!i!xi=i=2n2xi B ( x ) = ∑ i = 4 n i ! 2 i x i i ! = ∑ i = 4 n x i 2 i B(x)=\sum_{i=4}^n \frac{i!}{2i}\frac{x^i}{i!}=\sum_{i=4}^n \frac{x^i}{2i} B(x)=i=4n2ii!i!xi=i=4n2ixi
要注意 A ( x ) A(x) A(x)的一次项为 0 0 0 B ( x ) B(x) B(x)在次数等于 3 3 3时值为 0 0 0

由于整张图是由 A , B A,B A,B的并集拼出来的,所以答案的生成函数是 e A + B e^{A+B} eA+B,这里需要用到多项式的 e x p exp exp

解释一下上面那句话,答案的多项式应该是 A ∞ B ∞ A^\infty B^\infty AB,也就是环和链都是能无限选的,为什么要用 e A e^A eA呢?我们可以把它拆开,也就是 e A = ∏ i = 1 n e a i x i e^A=\prod_{i=1}^n e^{a_ix^i} eA=i=1neaixi,我们把上面的柿子泰勒展开,也就是把 a i x i a_ix^i aixi当做 x x x
e A = ∏ i = 1 n ∑ j = 0 ∞ ( a i x i ) j j ! e^A=\prod_{i=1}^n \sum_{j=0}^\infty \frac{(a_ix^i)^j}{j!} eA=i=1nj=0j!(aixi)j那么我们为什么要带上 j ! j! j!呢?这就要从这个条件:n个点是互异的讲起,请看下图:
在这里插入图片描述
计算的过程应该是先求 n n n个数的排列,再分成链和环,每个链和环内部交给它自己排列,所以要用 EGF 除去外面给它的排列,然后相同长度的环和链之间是可以交换的,所以要除去它们的排列,这就是带上 1 j ! \frac{1}{j!} j!1原因,不仅如此, e e e还能保证无限选,所以我们用多项式 e x p exp exp

最后补充一点,由于此题的 n n n实在是太大了,用 NTT \text{NTT} NTT时如果求两个长度为 [ 0 , 2 x ) [0,2^x) [0,2x)多项式的卷积,算长度只能这么算:

len=1;while(len<2*n) len<<=1;

而不能这么算:

len=1;while(len<=2*n) len<<=1;

因为加上等于号,长度就可能超过 2 21 2^{21} 221次方,原根的处理就会出错,最大的数据点会过不了。

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 3000005;
const int MOD = 1004535809;
int read()
{
    int num=0,flag=1;
    char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,lg,Rev[MAXN],A[MAXN],B[MAXN],C[MAXN],D[MAXN];
int a[MAXN],b[MAXN],c[MAXN],fac[MAXN];
void init(int n)
{
    fac[0]=1;
    for(int i=1; i<=n; i++) fac[i]=fac[i-1]*i%MOD;
}
int qkpow(int a,int b)
{
    int res=1;
    while(b>0)
    {
        if(b&1) res=res*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return res;
}
void NTT(int *a,int len,int tmp)
{
    lg=(int)round(log2(len));
    for(int i=0; i<len; i++)
    {
        Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
        if(i<Rev[i])
            swap(a[i],a[Rev[i]]);
    }
    for(int s=2; s<=len; s<<=1)
    {
        int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
        for(int i=0; i<len; i+=s)
        {
            int x=1;
            for(int j=0; j<t; j++,x=x*w%MOD)
            {
                int fe=a[i+j],fo=a[i+j+t];
                a[i+j]=(fe+x*fo)%MOD;
                a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
            }
        }
    }
    if(tmp==1) return ;
    int inv=qkpow(len,MOD-2);
    for(int i=0; i<len; i++)
        a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
    len=1;
    while(len<2*n) len<<=1;
    for(int i=0; i<len; i++) A[i]=B[i]=0;
    for(int i=0; i<n; i++) A[i]=a[i];
    for(int i=0; i<n; i++) B[i]=b[i];
    NTT(A,len,1);
    NTT(B,len,1);
    for(int i=0; i<len; i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i]%MOD,A[i]=(A[i]%MOD+MOD)%MOD;
    NTT(A,len,-1);
    for(int i=0; i<n; i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int n,int *a,int *b)
{
    int cur=1;
    for(int i=0; i<n; i++) b[i]=0;
    b[0]=qkpow(a[0],MOD-2);
    while(cur<n)
    {
        cur<<=1;
        work(cur,a,b);
    }
}
void mul(int n,int *a,int *b,int *c)
{
    len=1;
    while(len<2*n) len<<=1;
    for(int i=0; i<len; i++) A[i]=B[i]=0;
    for(int i=0; i<n; i++) A[i]=a[i];
    for(int i=0; i<n; i++) B[i]=b[i];
    NTT(A,len,1);
    NTT(B,len,1);
    for(int i=0; i<len; i++) A[i]=A[i]*B[i]%MOD;
    NTT(A,len,-1);
    for(int i=0; i<n; i++)
        c[i]=A[i];
}
void ln(int n,int *a,int *b)
{
    for(int i=0; i<n; i++)
        C[i]=a[i];
    inv(n,C,D);
    for(int i=0; i<n; i++)
        C[i]=C[i+1]*(i+1)%MOD;
    mul(n,C,D,C);
    b[0]=0;
    for(int i=1; i<n; i++)
        b[i]=C[i-1]*qkpow(i,MOD-2)%MOD;
}
void exp(int n,int *a,int *b)
{
    int cur=1;
    b[0]=1;
    while(cur<n)
    {
        cur<<=1;
        ln(cur,b,c);
        for(int i=0; i<cur; i++)
            c[i]=a[i]-c[i];
        c[0]++;
        len=1;
        while(len<2*cur) len<<=1;
        NTT(b,len,1);
        NTT(c,len,1);
        for(int i=0; i<len; i++) b[i]=b[i]*c[i]%MOD;
        NTT(b,len,-1);
    }
}
signed main()
{
    n=read();
    init(n);
    a[1]=1;
    int inv2=qkpow(2,MOD-2);
    for(int i=2; i<=n; i++) a[i]=inv2;
    for(int i=4; i<=n; i++) if(i%3) a[i]=(a[i]+qkpow(2*i,MOD-2))%MOD;
    exp(n+1,a,b);
    printf("%lld\n",b[n]*fac[n]%MOD);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值