码迷,mamicode.com
首页 > 其他好文 > 详细

$[SDOI2015]$序列统计

时间:2018-02-06 18:05:26      阅读:167      评论:0      收藏:0      [点我收藏+]

标签:状态   准备工作   span   display   scan   元素   概念   答案   using   

题目大意

给定集合\(S\),里面的元素都是小于\(M\)的非负整数。
然后生成一个长度为\(N\)的数列,数列中的每个数都属于集合\(S\)
现在的问题是:给定整数\(x\)
求所有可以生成出的,且满足数列中所有数的乘积\(mod\ M\)的值等于\(x\)的 不同的数列的有多少个。
答案对\(1004535809\)取模。

样例输入:
\(4\ \ \ 3\ \ \ 1\ \ \ 2\)
\(1\ \ \ 2\)
样例输出:
\(8\)
样例解释:请自己yy

数据范围:\(1\leq N\leq10^9\)\(3\leq M\leq 8000\)\(M\)为质数,\(1\leq x\leq M-1\) ;

思路与解法

高神推荐的动规题目,果然带有浓重的数学色彩......
首先最朴素的\(DP\)大家都会:
\(f[i][j]\)表示做到了序列的第\(i\)个位置,乘积为\(j\)的方案数,转移:。
\[f[i][\ \ (j*s[x]\%M)\ \ ] =\sum f[i-1][j]\]
其中\(s[x]\)\(S\)中的一个元素。然而这样就没法优化了......
所以我们要引入原根的概念(不是很早以前就引入了吗?这里回顾一下):

一个数\(p\)的原根\(g\),它满足这样一个性质:
\(r(i) = g^i \% p\) , 当\(i \in [0,p-2]\)时,\(r(i)\)取遍 \([1,p-1]\)
怎么求怎么用之类的网上很多自己看(其实是我懒得写),给一下模板:戳我!

所以我们把\(a * b\equiv x\ (mod \ p)\)中的\(a\)\(b\)\(x\)都换成\(g^i\)
然后给每一个\(g^i\)\(i\)建立映射关系。
那么原来的(\(a*b=x\))\(即(g^i*g^j = g^k)\)就变为了\((i+j=k)\)的加法运算。
这个有什么用呢? 重写一下之前的\(DP\)式:
\(f[i][j]\)表示做到了序列的第\(i\)个位置,状态为\(j\)(\(j\)\(g^t\)中的一个\(t\))的方案数。
我们把状态第一维舍去,写出方程式:
\[f[\ (j_1+j_2)\%(p-1)\ ] = \sum f[ j_1 ]*exist(j_2)\]
其中\(exist(j_2)\)表示的是\(g^{j_2}\)\(S\)中是否存在,显然可以预处理。
上面那个是标准卷积形式( \(\ \sum f(i)*\sum exist(j)\ \) ),直接快速幂加\(NTT\)一波即可。

实现代码:

注:本题数据极坑,请自觉把集合\(S\)中的\(0\)元素扔掉。

#include<bits/stdc++.h>
#define RG register
#define IL inline
#define ll long long
#define mod 1004535809
#define gi(xx) scanf("%lld",&xx)
#define _ 1000005
using namespace std;

ll f[_] , exist[_] , f1[_] , f2[_]; int prm[_],id[_];
ll n , m , s, x , l , tot , R[_] , len , ef; ll g , wn[30];

IL ll Pow(RG ll T,RG ll js,RG ll S,RG ll MOD){
    while(js){
        if(js & 1)S = (S * T) % MOD;
        T = T * T % MOD; js >>= 1;
    }return S;
}//快速幂

IL void Get_Pr_Root(){
    RG ll tmp = m - 1;
    for(RG int i = 2; i <= sqrt(tmp); i ++){
        if(tmp % i == 0){
            while(tmp % i == 0)tmp /= i;
            prm[ ++ tot ] = i;
    }}
    if(tmp ^ 1)prm[ ++ tot] = tmp;
    for(g = 2; g <= m - 1; g ++){
        RG bool flag = true;
        for(RG int i = 1; i <= tot; i ++)
            if(Pow( g , (m-1)/prm[i] , 1 , m) == 1)
                { flag = false ;  break; }
        if(flag)break;
    }return;
}//求解m的原根

IL void Give_ID(){
    for(RG int i = 0; i < m - 1; i ++){
        RG ll data = Pow( g , i , 1 , m);
        id[ data ] = i;
    }return;
}//建立对应映射关系

const ll pr = 3;        //3是1004535809 的原根
IL void Pre(){
    len = 2 * (m-2);
    for(ef = 1; ef <= len; ef<<=1) ++ l;
    for(RG int i = 0; i < ef; i ++)
        R[i] = (R[i>>1] >> 1) | ((i&1) << (l-1));
    for(RG int i = 0; i < 25; i ++){
        RG ll tt = (1<<i);
        wn[i] = Pow(pr , (mod-1) / tt , 1 , mod);
    }return;
}//NTT的准备工作

IL void NTT(RG ll *P , RG ll opt){
    for(RG int i = 0; i < ef; i ++)
        if(i < R[i]) swap(P[i] , P[R[i]]);
    for(RG int i = 1,id =0; i < ef; i <<= 1){
        ++ id;
        for(RG int j = 0,p = i<<1 ; j < ef; j += p){
            RG ll w = 1;
            for(RG int k = 0; k < i; k ++,w = w*wn[id]%mod){
                RG ll X = P[j + k] , Y = w*P[j + k + i];
                P[j + k] = (X + Y) % mod;
                P[j + k + i] = ((X - Y)%mod + mod) % mod;
    }}}
    if(opt < 0){
        for(RG int i = 1; i < ef/2; i ++)swap(P[i] , P[ef-i]);
        RG ll inv = Pow(ef , mod - 2, 1 , mod);
        for(RG int i = 0; i < ef; i ++)P[i] = P[i] % mod * inv % mod;
    }return;
}//NTT计算卷积

IL void Mul(RG ll *ans , RG ll *itm){
    for(RG int i = 0; i < ef; i ++)f1[i] = ans[i];
    for(RG int i = 0; i < ef; i ++)f2[i] = itm[i];
    NTT(f1,1); NTT(f2,1);
    for(RG int i = 0; i < ef; i ++)f1[i] = (f1[i] * f2[i]) % mod;
    NTT(f1,-1);
    for(RG int i = 0; i < m-1; i ++)
        ans[i] = ( f1[i] + f1[i+m-1] ) % mod;
    return;
}//进行DP转移

IL void Work(){
    Pre(); f[id[1]] = 1;
    while( n ){
        if(n & 1)Mul(f , exist);
        Mul(exist , exist);  n >>= 1;
    }return;
}//快速幂求解答案f

int main(){
    gi(n); gi(m); gi(x); gi(s);
    Get_Pr_Root();
    Give_ID();
    RG ll si; while( s -- ){ gi(si); if(si)exist[id[si]] = 1; }
    Work();
    printf("%lld" , f[id[x]]); return 0;
}

$[SDOI2015]$序列统计

标签:状态   准备工作   span   display   scan   元素   概念   答案   using   

原文地址:https://www.cnblogs.com/GuessYCB/p/8423114.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!