【集训队互测2013】城市规划 (多项式求逆或分治FFT)

Description:

刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了.

刚才说过, 阿狸的国家有n 个城市, 现在国家需要在某些城市对之间建立一些贸易路线, 使得整个国家的任意两个城市都直接或间接的连通.

为了省钱, 每两个城市之间最多只能有一条直接的贸易路径. 对于两个建立路线的方案, 如果存在一个城市对, 在两个方案中是否建立路线不一样, 那么这两个方案就是不同的, 否则就是相同的. 现在你需要求出一共有多少不同的方案.

好了, 这就是困扰阿狸的问题. 换句话说, 你需要求出n 个点的简单(无重边无自环)无向连通图数目.

由于这个数字可能非常大, 你只需要输出方案数mod 1004535809(479 * 2 ^21 + 1)即可.

n <= 130000

题解:

这个动态规划的关键在于确立一个标号,枚举它所在的连通块。

fi表示答案。

多项式求逆:

fn=2C2nn1i=1Ci1n1fi2C2ni

2C2n=fn+n1i=1Ci1n1fi2C2ni

2C2n=ni=1Ci1n1fi2C2ni

2C2n/(n1)!=ni=1fi/(i1)!2C2ni/(ni)!

Fn=2C2n/(n1)!Gi=fi/(i1)!,Hi=2C2i/(i)!

显然有F=GH,G=FH1

于是对H进行多项式求逆,搞一下就能求出F,进而推出f。

分治:

fn=2C2nn1i=1Ci1n1fi2C2ni
fn=2C2n(n1)!n1i=1fi/(i1)!2C2ni/(ni)!

用cdq分治的思想。

Solve(x,y)

设m=x+y>>1

先Solve(x,m)

这样就求出了f(x..m)

看作多项式乘上另一个多项式,再把得出的结果一一加给m+1..y,继续分治。

Code(多项式求逆):

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define ff(i, x, y) for(int i = x; i < y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
using namespace std;

const ll mo = 1004535809;

const int N = 130005 * 4;

int n, m, tp, tx;
ll a[N], b[N], b1[N], b2[N], c[N], fac[N], nf[N], w[N];

ll ksm(ll x, ll y) {
    ll s = 1;
    for(; y; y >>= 1, x = x * x % mo)
        if(y & 1) s = s * x % mo;
    return s;
}

void dft(ll *a, int n) {
    ff(i, 0, n) {
        int p = i, q = 0;
        fo(j, 1, tx) q = q * 2 + p % 2, p /= 2;
        if(q > i) swap(a[q], a[i]);
    }
    for(int m = 2; m <= n; m *= 2) {
        int h = m / 2;
        ff(i, 0, h) {
            ll W = w[i * (n / m)];
            for(int j = i; j < n; j += m) {
                int k = j + h;
                ll u = a[j], v = a[k] * W % mo;
                a[j] = (u + v) % mo; a[k] = (u - v + mo) % mo;
            }
        }
    }
}
void fft(ll *a, ll *b, int n) {
    ll v = ksm(3, (mo - 1) / n); w[0] = 1;
    fo(i, 1, n) w[i] = w[i - 1] * v % mo;
    dft(a, n); dft(b, n); ff(i, 0, n) a[i] = a[i] * b[i] % mo;
    fo(i, 0, n / 2) swap(w[i], w[n - i]);
    dft(a, n); v = ksm(n, mo - 2);
    ff(i, 0, n) a[i] = a[i] * v % mo;
}

int main() {
    scanf("%d", &n); while(1 << ++ tp <= n);
    fac[0] = 1; fo(i, 1, n) fac[i] = fac[i - 1] * i % mo;
    nf[n] = ksm(fac[n], mo - 2); fd(i, n - 1, 0) nf[i] = nf[i + 1] * (i + 1) % mo;
    fo(i, 0, n) c[i] = ksm(2, (ll) i * (i - 1) / 2) * nf[i];
    fo(i, 1, n) a[i] = ksm(2, (ll) i * (i - 1) / 2) * nf[i - 1];
    b[0] = ksm(c[0], mo - 2);
    fo(ii, 1, tp) {
        m = 1 << ii;
        memcpy(b1, b, sizeof b); memcpy(b2, b, sizeof b);
        memset(b, 0, sizeof b);
        tx = ii + 1;
        ff(j, 0, m) b[j] = c[j];
        fft(b, b1, m * 2);
        ff(i, m, m * 2) b[i] = 0;
        ff(i, 0, m) b[i] = (mo - b[i]) % mo; b[0] = (b[0] + 2) % mo;
        fft(b, b2, m * 2);
        ff(i, m, m * 2) b[i] = 0;
    }
    fft(a, b, 1 << tx);
    ll ans = a[n] * fac[n - 1] % mo;
    printf("%lld", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值