BZOJ 3992 SDOI 2015 序列统计 NTT 生成函数 计数 原根

本文介绍了一种高效算法,用于计算模意义下数列乘积等于特定值的不同数列的数量。通过将问题转化为多项式运算,并利用快速傅立叶变换进行加速,解决了大数范围内的组合计数问题。

给定元素在[0,m)内的整数集合S,求有多少个长度为n的数列满足所有元素属于S且mod m下的积为x。

由于m是质数,所以元素的积利用原根可转化为和。

eiseqeixmodm

由于原根的幂可以一一表示[0,m)内的数,所以
hi,hxghimodm=ei,ghxmodm=x
ghighixghxmodm

hihxmodm1

于是问题转化为在S中可重复地选出n个元素使和为指定值。
考虑写个DP,然后发现和多项式乘法可以对应上。
而如果令第 hi 项的系数为1,那么该多项式的 n 次幂的第hx项系数就是答案了。
多项式快速幂即可。
乘的时候注意元素和取模。

求原根之法:http://blog.youkuaiyun.com/acdreamers/article/details/8883285
然后1004535809的原根是3

#include <cstdio>
#include <algorithm>
#define rep(i,x,y) for(i=x;i<y;++i)
#define FOR(i,x,y) for(i=x;i<=y;++i)
using namespace std;
typedef long long ll;
const int MOD = 1004535809, N = 18000;
ll qpow(ll a, ll b, ll mod = MOD) {
    ll c = 1;
    for ( ; b; b >>= 1, a = a * a % mod)
        if (b & 1) c = c * a % mod;
    return c;
}
struct Alg {
    int n, m, rev[N];
    ll inv_n, w[2][N], t[N];
    void init(int c) {
        m = c; int k = -1;
        for (n = 1; n < 2 * m - 1; n <<= 1) ++k; inv_n = qpow(n, MOD - 2);
        int G = qpow(3, (MOD - 1) / n), i;
        w[0][0] = w[1][0] = w[0][n] = w[1][n] = 1;
        rep(i,1,n) w[0][i] = w[1][n - i] = w[0][i - 1] * G % MOD;
        rep(i,0,n) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << k);
    }
    void fnt(ll *a, int f) {
        int i, j, k;
        rep(i,0,n) if (rev[i] < i) swap(a[i], a[rev[i]]);
        for (i = 2; i <= n; i <<= 1)
            for (j = 0; j < n; j += i)
                rep(k, 0, i / 2) {
                    ll x = a[j + k], y = a[j + k + i / 2] * w[f][k * (n / i)] % MOD;
                    a[j + k] = (x + y) % MOD, a[j + k + i / 2] = (x - y + MOD) % MOD;
                }
        if (f) rep(i,0,n) a[i] = a[i] * inv_n % MOD;
    }
    void mul(ll *a, ll *b) {
        int i;
        rep(i,0,n) t[i] = b[i];
        fnt(a, 0), fnt(t, 0);
        rep(i,0,n) a[i] = a[i] * t[i] % MOD;
        fnt(a, 1);
        for(i=n-1;i>=m-1;--i) (a[i - m + 1] += a[i]) %= MOD, a[i] = 0;
    }
} fnt;
int get_root(int m) {
    static int g[N];
    int i, j, t = m - 1, c = 0;
    FOR(i,2,t) if (t % i == 0) {
        g[c++] = i; while (t % i == 0) t /= i;
    }
    for (i = 2; ; ++i) {
        rep(j,0,c) if (qpow(i, (m - 1) / g[j], m) == 1) break;
        if (j == c) return i;
    }
}
int h[N]; ll a[N], b[N];
int main() {
    int i, e, n, m, x, s, g = 2;
    scanf ("%d%d%d%d", &n, &m, &x, &s);
    g = get_root(m);
    int t = 1; h[1] = 0;
    rep(i, 1, m - 1) t = t * g % m, h[t] = i;
    fnt.init(m);
    rep(i, 0, s) {
        scanf("%d", &e);
        if (e) a[h[e]] = 1;
    }
    for (b[0] = 1; n; fnt.mul(a, a), n >>= 1)
        if (n & 1) fnt.mul(b, a);
    printf("%lld", b[h[x]]);
    return 0;
}

3992: [SDOI2015]序列统计

Description

小C有一个集合S,里面的元素都是小于M的非负整数。他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S。
小C用这个生成器生成了许多这样的数列。但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中所有数的乘积mod M的值等于x的不同的数列的有多少个。小C认为,两个数列{Ai}和{Bi}不同,当且仅当至少存在一个整数i,满足Ai≠Bi。另外,小C认为这个问题的答案可能很大,因此他只需要你帮助他求出答案mod 1004535809的值就可以了。

Input

一行,四个整数,N、M、x、|S|,其中|S|为集合S中元素个数。第二行,|S|个整数,表示集合S中的所有元素。

Output

一行,一个整数,表示你求出的种类数mod 1004535809的值。

Sample Input

4 3 1 2
1 2

Sample Output

8

HINT

【样例说明】

可以生成的满足要求的不同的数列有(1,1,1,1)、(1,1,2,2)、(1,2,1,2)、(1,2,2,1)、(2,1,1,2)、(2,1,2,1)、(2,2,1,1)、(2,2,2,2)。

【数据规模和约定】

对于10%的数据,1<=N<=1000;

对于30%的数据,3<=M<=100;

对于60%的数据,3<=M<=800;

对于全部的数据,1<=N<=109,3<=M<=8000,M为质数,1<=x<=M-1,输入数据保证集合S中元素不重复

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值