zoukankan      html  css  js  c++  java
  • BZOJ3992: [SDOI2015]序列统计(NTT 原根 生成函数)

    题意

    题目链接

    给出大小为(S)的集合,从中选出(N)个数,满足他们的乘积(\% M = X)的方案数

    Sol

    神仙题Orz

    首先不难列出最裸的dp方程,设(f[i][j])表示选了(i)个数,他们的乘积为(j)的方案数

    (g[k] = [exists a_i = k])

    转移的时候

    [f[i + 1][(j * k) \% M] += f[i][j] * g[k] ]

    不难发现每次的转移都是相同的,因此可以直接矩阵快速幂,时间复杂度变为(logN M^2)

    观察上面的式子,如果我们能把((j * k) \% M),变成((j + k) \% M)的话,就是一个循环卷积的形式了

    这里可以用原根来实现,设(g)表示(M)的原根,(mp[i] = j)表示(g^j = i)

    直接对每个物品构造生成函数,利用mp转移即可

    因为转移是个循环卷积,所以统计答案的时候应该把第(i)项和第(i+m-1)项的系数加起来

    至于为啥只统计一项。

    #include<bits/stdc++.h>
    using namespace std;
    const int mod = 1004535809, G = 3, Gi = 334845270, MAXN = 1e5 + 10; 
    inline int read() {
        char c = getchar(); int x = 0, f = 1;
        while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    int N, M, X, S;
    int r[MAXN], lim, L, ind[MAXN], s[MAXN], f[MAXN], a[MAXN], b[MAXN];
    int mul(int a, int b) {
        return 1ll * a * b % mod;
    }
    int add(int x, int y) {
        if(x + y < 0) return x + y + mod;
        return x + y >= mod ? x + y - mod : x + y;
    }
    int dec(int x, int y) {
        return x - y < 0 ? x - y + mod : x - y;
    }
    int fp(int a, int p, int mod) {
        int base = 1;
        while(p) {
            if(p & 1) base = 1ll * base * a % mod; 
            a = 1ll * a * a % mod; p >>= 1;
        }
        return base;
    }
    int GetG(int x) {
        static int q[MAXN]; int tot = 0, tp = x - 1;
        for(int i = 2; i * i <= tp; i++) {
            if(!(tp % i)) {
                q[++tot] = i;
                while(!(tp % i)) tp /= i;
            }
        }
        if(tp > 1) q[++tot] = tp;
        for(int i = 2, j; i <= x - 1; i++) {
            for(j = 1; j <= tot; j++) if(fp(i, (x - 1) / q[j], x) == 1) break;
            if(j == tot + 1) return i;
        }
    }
    void NTT(int *a, int N, int type) {
        for(int i = 1; i < N; i++) if(i < r[i]) swap(a[i], a[r[i]]);
        for(int mid = 1; mid < N; mid <<= 1) {
            int R = mid << 1, Wn = fp(type == 1 ? G : Gi, (mod - 1) / R, mod);
            for(int j = 0; j < lim; j += R) {
                for(int w = 1, k = 0; k < mid; k++, w = mul(w, Wn)) {
                    int x = a[j + k], y = mul(w, a[j + k + mid]);
                    a[j + k] = add(x, y);
                    a[j + k + mid] = dec(x, y);
                }
            }
        }
        if(type == -1) {
            for(int i = 0, inv = fp(lim, mod - 2, mod); i < N; i++) a[i] = mul(a[i], inv);
        }
    }
    void mul(int *a1, int *b1, int *c) {
        memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));//tag
        for(int i = 0; i < M - 1; i++) a[i] = a1[i], b[i] = b1[i];
        NTT(a, lim, 1); NTT(b, lim, 1);
        for(int i = 0; i < lim; i++) a[i] = mul(a[i], b[i]);
        NTT(a, lim, -1);
        for(int i = 0; i < M - 1; i++) c[i] = add(a[i], a[i + M - 1]);
    }
    void Pre() {
        lim = 1;
        while(lim <= 2 * (M - 2)) lim <<= 1, L++;
        for(int i = 0; i < lim; i++) r[i] = (r[i >> 1] >> 1) | (i & 1) << (L - 1);
        int d = GetG(M);
        for(int i = 0; i < M - 1; i++) ind[fp(d, i, M)] = i;
    }
    int main() {
        N = read(); M = read(); X = read(); S = read();
        Pre();
        for(int i = 1; i <= S; i++) {
            int x = read();
            if(x) f[ind[x]]++;
        }
        s[ind[1]] = 1;
        while(N) {
            if(N & 1) mul(s, f, s);
            mul(f, f, f); N >>= 1;
        }
        printf("%d", s[ind[X]]);
        return 0;
    }
    /*
    40000000 3 1 2
    1 2
    
    4 3 1 2
    1 2
    */
    
  • 相关阅读:
    对百度搜索引擎的评论
    团队开发个人总结05
    Bootsrap下拉菜单实现Hover下拉效果
    C#抽奖优惠券生成唯一码方法
    js.live方法无效, 报错:uncaught TypeError: $(...).live is not a function
    SQL 插入一条自定义主键值的数据
    一款很简单的直接发送邮件功能
    SQL生成指定范围内随机值
    SQL(replace)替换字段中指定的字符
    SQL表中删除一列
  • 原文地址:https://www.cnblogs.com/zwfymqz/p/10028529.html
Copyright © 2011-2022 走看看