[bzoj3992][SDOI2015]序列统计——离散对数+NTT

2 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

给定一个数字不超过 m m m的集合 S S S,用 S S S中的数生成一个长度为 n n n的序列,求所有序列中的元素乘积模 m m m等于 x x x的序列的个数。

思路:

考虑最朴素的 D P DP DP,设 f i , j f_{i,j} fi,j为选了 i i i个数,乘积模 m m m j j j的方案数,直接转移的时间复杂度是 O ( n m 2 ) O(nm^2) O(nm2)的。

不难发现每次转移的过程是相同的,矩阵加速显然不太可行,考虑将乘法形式的转移变成加法形式的转移,这样每次转移即可用NTT优化。

这里需要用到一个叫做离散对数的东西,即在取模的意义下,将每个 m m m以内的数都表示为 g x g^x gx幂的形式,这里的 g g g为模 m m m意义下的原根。

这样我们将每个数对 g g g取对数之后,每次转移便可以用NTT来优化了,但是 n n n很大还是个问题,这个时候发现多项式乘法也是满足结合律的,既然每次的转移多项式是一样的,直接上快速幂即可。

 
/*=======================================
 * Author : ylsoi
 * Time : 2019.2.4
 * Problem : bzoj3992
 * E-mail : ylsoi@foxmail.com
 * ====================================*/
#include<bits/stdc++.h>
 
#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;
 
using namespace std;
 
void File(){
    freopen("bzoj3992.in","r",stdin);
    freopen("bzoj3992.out","w",stdout);
}
 
template<typename T>void read(T &_){
    _=0; T fl=1; char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')fl=-1;
    for(;isdigit(ch);ch=getchar())_=(_<<1)+(_<<3)+(ch^'0');
    _*=fl;
}
 
const int maxm=8000+10;
const int mod=1004535809;
int n,m,aim,sz,t[maxm];
bool s[maxm];
 
ll qpow(ll x,ll y){
    ll ret=1; x%=mod;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret;
}
 
int lim,cnt,dn[maxm<<2];
ll g[maxm<<2],ig[maxm<<2];
 
void ntt(ll *A,int ty){
    REP(i,0,lim-1)if(i<dn[i])swap(A[i],A[dn[i]]);
    for(int len=1;len<lim;len<<=1){
        ll w= ty==1 ? g[len<<1] : ig[len<<1];
        for(int L=0;L<lim;L+=len<<1){
            ll wk=1;
            REP(i,L,L+len-1){
                ll u=A[i],v=A[i+len]*wk%mod;
                A[i]=(u+v)%mod;
                A[i+len]=(u-v)%mod;
                wk=wk*w%mod;
            }
        }
    }
    if(ty==-1){
        ll inv=qpow(lim,mod-2);
        REP(i,0,lim-1)A[i]=A[i]*inv%mod;
        REP(i,m-1,lim-1){
            A[i%(m-1)]=(A[i%(m-1)]+A[i])%mod;
            A[i]=0;
        }
    }
}
 
void init(){
    read(n),read(m),read(aim),read(sz);
    int x;
    REP(i,1,sz)read(x),s[x]=1;
    REP(i,2,m-1){
        x=i;
        ll w=x;
        REP(j,1,m-2){
            if(w==1){
                x=-1;
                break;
            }
            w=w*x%m;
        }
        if(x==i)break;
    }
    for(ll i=1,j=0;j<m-1;i=i*x%m,++j)
        t[i]=j;
    lim=1,cnt=0;
    while(lim<=m+m)lim<<=1,++cnt;
    if(!cnt)cnt=1;
    REP(i,0,lim-1)dn[i]=dn[i>>1]>>1|((i&1)<<(cnt-1));
    g[lim]=qpow(3,(mod-1)/lim);
    ig[lim]=qpow(g[lim],mod-2);
    for(int i=lim>>1;i;i>>=1){
        g[i]=g[i<<1]*g[i<<1]%mod;
        ig[i]=ig[i<<1]*ig[i<<1]%mod;
    }
}
 
ll a[maxm<<2],b[maxm<<2];
 
void work(){
    a[0]=1;
    REP(i,1,m-1)if(s[i])b[t[i]]=1;
    while(n){
        ntt(b,1);
        if(n&1){
            ntt(a,1);
            REP(i,0,lim-1)a[i]=a[i]*b[i]%mod;
            ntt(a,-1);
        }
        REP(i,0,lim-1)b[i]=b[i]*b[i]%mod;
        ntt(b,-1);
        n>>=1;
    }
    printf("%lld\n",(a[t[aim]]+mod)%mod);
}
 
int main(){
    //File();
    init();
    work();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值