zoukankan      html  css  js  c++  java
  • bzoj3992 [SDOI2015]序列统计(从一道题入手NTT)

    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中元素不重复

    分析:
    先报算法:NTT
    这种东西的思路都是
    找多项式,看出是卷积形式,然后套板子

    好像做过一道很像的题呢
    那道题是叫你统计n个数相加,模数一定的方案数

    那我们第一件事就是想dp啊
    f[i][j]=sigma f[i-1][j*inv[k]] //inv是逆元

    这个复杂度显然不科学
    我们先放下这个问题,看一种新的算法:

    FNT/NTT

    这里写图片描述
    这里写图片描述
    这里写图片描述

    诶诶诶
    这个模数怎么这么熟悉
    1004535809
    那这道题我们要是能找到卷积的形式
    就可以套NTT板子了

    归根到底
    g^(p-1)同余于1(mod p)
    则称g是p的原根

    求原根目前的做法只能是从2开始枚举,
    然后暴力判断g^(p-1)同余于1 (mod p)
    是否当且仅当指数为p-1的时候成立
    而由于原根一般都不大,所以可以暴力得到

    当然一些常见的模数是要记住的
    像这道题中,1004535809就是一个原根为3的经典模数

    找到了学姐的blog,讲的蛮不错
    http://blog.csdn.net/clover_hxy/article/details/56495911

    我在这里说一下自己的理解:

    原根的次方可以表示p以内的任意数字,因为是模意义下的运算
    我们当然可以用原根的任意次幂表示任意数字(mod p)
    这样我们可以把之前的乘法变成一个加法

    新的状态就变成了

    f[i][j]表示选到第i个数,结果是g^j的方案数

    f[i][j+k]+=f[i-1][j] //同底数幂相乘,底数不变,指数相加

    用h[i]表示原根的i次幂的方案数,q[i]表示原根的i次幂是否在集合中

    h[i]=sigma h[j]*q[i-j]

    这就是一个卷积的形式了

    tip

    主程序中是正常的预处理
    fn很容易带成n

    原根的求法觉得很值得记下:
    这里写图片描述

    为了快速处理
    我们用了一个类似于快速幂的东西

    mul的过程和FFT很像,唯一需要注意的是,最后在/fn的时候
    要处理出一个逆元,化除为乘

    NTT的过程和FFT基本一样

    这是分割线———————————————————————————————-

    查了半天错,才发现是自己定义的一个swap函数
    没有带取地址符。。。orz

    在NTT函数中,变量名老是搞错
    现在才明白执行语句顺序的重要性
    以前写FFT的时候,
    a[m+j+k]的操作总是在a[j+k]之前,我也没怎么注意
    结果这次顺序写反,就jj了

    没有好好读题
    题目是让在S中选择n个数,S中每个数的大小都不超过m,个数是tot

    在传参进入slove的时候,应该传n,QAQ

    那么卷机过后的数组长度就是2*(m-1)

    WA.st
    快速幂少%了一下
    求原根的时候KSM(i,(x-1)/j,x)
    WA.nd
    lld<–I64d

    总觉得这道题真的是很艰难的A了

    这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #define ll long long
    
    using namespace std;
    
    const int N=17000;
    const ll mod=1004535809;
    int n,m,X,tot,yg,M,fn,R[N];   
    bool vis[N];
    ll h[N],a[N],b[N],q[N];
    
    ll KSM(ll a,ll b,ll p)
    {
        a%=p;
        ll t=1;
        while (b)
        {
            if (b&1)
               t=(t*a)%p;
            a=(a*a)%p;
            b>>=1;
        }
        return t%p;
    }
    
    int calc(int x)   //求原根
    {
        if (x==2) return 1;
        for (int i=2;i;i++)
        {
            bool pp=1;
            for (int j=2;j*j<x;j++)
                if (KSM(i,(x-1)/j,x)==1)   
                //只要有一个非mod-1的数幂也同余于1,就不合法 
                {
                    pp=0;
                    break;
                }
            if (pp) return i;
        }
    } 
    
    void NTT(ll *a,int n,int opt)
    {
        int i,j=0,k;
        for (i=0;i<n;i++)
        {
            if (i>j) swap(a[i],a[j]);
            for (int l=n>>1;(j^=l)<l;l>>=1);
        }
        for (i=1;i<n;i<<=1)
        {
            ll wn=KSM(3,(mod-1)/(i<<1),mod);  //1004535809原根 
            int m=i<<1;
            for (j=0;j<n;j+=m)
            {
                ll w=1;
                for (k=0;k<i;k++,w=(w*wn)%mod)
                {
                    ll z=(w*a[j+k+i])%mod;
                    a[j+k+i]=(a[j+k]-z+mod)%mod;
                    a[j+k]=(a[j+k]+z)%mod;
                }
            }
        }
        if (opt==-1) reverse(a+1,a+n);
    } 
    
    void mul(ll h[N],ll q[N])
    {
        for (int i=0;i<fn;i++) a[i]=h[i]%mod;
        for (int i=0;i<fn;i++) b[i]=q[i]%mod;
        NTT(a,fn,1); NTT(b,fn,1);
        for (int i=0;i<fn;i++) a[i]=(a[i]*b[i])%mod;
        NTT(a,fn,-1);
        ll inv=KSM(fn,mod-2,mod);   //逆元
        for (int i=0;i<fn;i++) a[i]=(a[i]*inv)%mod;  
        //因为是模意义下的整数除法,就不能像FFT一样这么耿直的直接/了 
        for (int i=0;i<m-1;i++)
            h[i]=(a[i]+a[i+m-1])%mod; 
    }
    
    void solve(int x)
    {
        h[0]=1;
        while (x)   //类似一个快速幂,快速处理卷积 
        { 
            if (x&1) mul(h,q);
            x>>=1;
            mul(q,q); 
        }
    }
    
    int main()
    {
        scanf("%d%d%d%d",&n,&m,&X,&tot);
        yg=calc(m);
        for (int i=1;i<=tot;i++)
        {
            int x;
            scanf("%d",&x);vis[x]=1;
        } 
        int pos=-1;
        for (int i=0,j=1;i<m-1;i++,j=(j*yg)%m)
        {
            if (vis[j]) q[i]=1;   //原根的i次幂在不在集合中 
            if (j==X) pos=i;   //答案是yg的pos次幂 
        }
        M=(m-1)*2; fn=1;
        while (fn<=M) fn<<=1;
        solve(n);
        if (pos!=-1) printf("%lld
    ",h[pos]%mod);
        else printf("0
    ");
        return 0;
    }
  • 相关阅读:
    HDU 5835 Danganronpa 贪心
    HDU 5842 Lweb and String 水题
    HDU 5832 A water problem 水题
    Codeforces Beta Round #14 (Div. 2) A. Letter 水题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem K. UTF-8 Decoder 模拟题
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem I. Alien Rectangles 数学
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem H. Parallel Worlds 计算几何
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem F. Turning Grille 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem C. Cargo Transportation 暴力
    Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem G. k-palindrome dp
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673370.html
Copyright © 2011-2022 走看看