【集训队互测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);
}
LGP10006[集训队 2023] 超现实树 NOI/NOI+/CTSC O2优化 2023 WC/CTSC/集训队分治 根号分治 快速数论变换 NTT 集训队 标准IO 传统题 来源 洛谷 时间限制 1000ms 内存限制 512MB 通过/尝试次数 0/46 题目背景 Alek 喜欢打信息竞赛,尤其喜欢超现实树。超现实树,顾名思义,就是树上的超现实数。 题目描述 Alek 认为,对于常数 𝑘 k,一个字符串被称为「 𝑘 k-超现实数串」,如果其只包含字符 { , | , } {,|,},且: 空串为 𝑘 k-超现实数串; 如果 𝑠 , 𝑡 s,t 为 𝑘 k-超现实数串,那么 𝑠 + 𝑡 s+t 为 𝑘 k-超现实数串; 如果 𝑘 + 1 k+1 个字符串 𝑠 1 , 𝑠 2 , ⋯   , 𝑠 𝑘 + 1 s 1 ​ ,s 2 ​ ,⋯,s k+1 ​ 都是 𝑘 k-超现实数串,那么 { + 𝑠 1 + | + 𝑠 2 + | + ⋯ + | + 𝑠 𝑘 + 1 + } {+s 1 ​ +|+s 2 ​ +|+⋯+|+s k+1 ​ +} 为 𝑘 k-超现实数串; 𝑘 k-超现实数串仅限于此。 给定一棵 𝑛 n 个点的无根树,节点编号为 1 ∼ 𝑛 1∼n。每个点 𝑖 i 上有一个字符 𝑎 𝑖 ∈ { { , | , } } a i ​ ∈{{,|,}}。 给定整数 𝑚 m,Alek 希望你对 𝑘 = 0 , 1 , ⋯   , 𝑚 k=0,1,⋯,m 分别出:有多少有序对 ( 𝑥 , 𝑦 ) (x,y), 1 ≤ 𝑥 , 𝑦 ≤ 𝑛 1≤x,y≤n,使得树上从点 𝑥 x 到点 𝑦 y 的唯一简单路径上的字符依次拼接所得字符串是 𝑘 k-超现实数串。 输入格式 第一行两个整数 𝑛 , 𝑚 n,m,分别表示树的节点数,和需要答案的 𝑘 k 的上限。 第二行一个字符串 𝑎 a, 𝑎 a 的第 𝑖 i 个字符表示点 𝑖 i 上的字符。 接下来 𝑛 − 1 n−1 行,每行两个整数 𝑥 , 𝑦 x,y,表示存在一条连接点 𝑥 x 和点 𝑦 y 的边。 输出格式 输出一行 𝑚 + 1 m+1 个整数,分别表示 𝑘 = 0 , 1 , ⋯   , 𝑚 k=0,1,⋯,m 时的答案。 输入 #1 运行 复制 5 3 |{}}} 2 1 3 2 4 1 5 1 输出 #1 复制 1 2 0 0 输入 #2 运行 复制 10 8 |}||}{|{{{ 2 1 3 1 4 3 5 2 6 5 7 5 8 4 9 2 10 3 输出 #2 复制 2 0 1 1 0 0 0 0 0 输入 #3 运行 复制 见附加文件 ex_surreal3.in。 输出 #3 复制 见附加文件 ex_surreal3.ans。 提示 对于所有数据,有 2 ≤ 𝑛 ≤ 1 0 5 2≤n≤10 5 , 0 ≤ 𝑚 ≤ 𝑛 − 2 0≤m≤n−2, 𝑎 𝑖 ∈ { { , | , } } a i ​ ∈{{,|,}}。 Subtask 1(5 分): 𝑛 ≤ 4601 n≤4601; Subtask 2(20 分):对每条边 ( 𝑥 , 𝑦 ) (x,y) 有 𝑦 = 𝑥 + 1 y=x+1; Subtask 3(5 分): 𝑎 𝑖 ≠ | a i ​  =|, 𝑚 = 0 m=0; Subtask 4(15 分,依赖 Subtask 3): 𝑚 ≤ 3 m≤3; Subtask 5(25 分,依赖 Subtask 1): 𝑛 ≤ 5 × 1 0 4 n≤5×10 4 ; Subtask 6(30 分,依赖 Subtask 1, 2, 3, 4, 5):无特殊限制。 C++写代码
最新发布
06-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值