[BZOJ3992][SDOI2015]序列统计(NTT+DP+ksm)

74 篇文章 0 订阅
15 篇文章 0 订阅

题目:

我是超链接

题解:

从简单开始吧。
首先我们假设题目让求的是“数列中所有数的【和】%mod=x的不同方案数”
那么设F[i][j]表示选择i个数%mod=j的方案数
转移方程 F[i][j]=|S|k=1F[i1][jSk] F [ i ] [ j ] = ∑ k = 1 | S | F [ i − 1 ] [ j − S k ]

这个形式我们可以转化为卷积的形式!
设b[i]表示第i个数字选了没0/1,令 a=F[i1] a = F [ i − 1 ] F[i]=F[i] F ′ [ i ] = F [ i ]
那么柿子可以转化成 F[i]=m1j=0a[ij]b[j] F ′ [ i ] = ∑ j = 0 m − 1 a [ i − j ] ∗ b [ j ]

这个玩意可以卷,可以发现卷一次是 F[i1]>F[i] F [ i − 1 ] − > F [ i ] ,那么我们卷n次就可以得到答案,可以用快速幂实现

那么我们转入正经的题目了,F的意义依然不变
转移方程 F[i][j]=|S|k=1F[i1][jSk] F [ i ] [ j ] = ∑ k = 1 | S | F [ i − 1 ] [ j S k ]

我们这里引入【原根】
假设一个数g是P的原根,那么 gi%P g i % P 的结果两两不同,且有 1<g<P0<i<P 1 < g < P , 0 < i < P ,归根到底就是 g(P1)=1(%P) g ( P − 1 ) = 1 ( % P ) 当且仅当指数为P-1的时候成立,P是素数。

我们可以发现一个素数只能有一个原根,并且这个原根从1~P-1次方可以取遍1~P-1所有的数字,这很显然,因为原根的次方项%P两两不同,只有P-1个,那肯定要取满P-1个数字咯

原根怎么求呢?从2开始枚举,然后暴力判断g^(P-1) = 1 (mod P)是否当且仅当指数为P-1的时候成立。

那么我们只要对于P找个原根g,这个乘除形式又可以变成加减了,比如说设 j=gxSk=gy j = g x , S k = g y
F[i][gx]=|S|k=1F[i1][gxy] F [ i ] [ g x ] = ∑ k = 1 | S | F [ i − 1 ] [ g x − y ] 同时我们的DP目标就变成了 F[n][gz] F [ n ] [ g z ] gz=X g z = X

整理一下柿子: F[i][x]=m1y=0[gyS]F[i1][xy] F [ i ] [ x ] = ∑ y = 0 m − 1 [ g y ∈ S ] F [ i − 1 ] [ x − y ]
至此我们又画出来了加减的形式,可以运用刚才说的快速幂+NTT解决了。

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int mod=1004535809;
const int N=300005;
int n,r[N],m;LL a[N],b[N],f[N],ans[N],inv;bool vis[N];
LL ksm(LL a,LL k,int mod)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod)
      if (k&1) ans=ans*a%mod;
    return ans;
}
int yg(int m)
{
    for (int i=2;i<=m;i++)
    {
        bool fff=0;
        for (int j=1;j<m-1;j++) 
          if (ksm(i,j,m)==1) {fff=1;break;}
        if (!fff) return i;
    }
}
void NTT(LL *a,int id)
{
    for (int i=0;i<n;i++)
      if (i<r[i]) swap(a[i],a[r[i]]);
    for (int k=1;k<n;k<<=1)
    {
        LL wn=ksm(3,(mod-1)/(k<<1),mod);
        for (int i=0;i<n;i+=(k<<1))
        {
            LL w=1;
            for (int j=0;j<k;j++,w=w*wn%mod)
            {
                LL x=a[i+j],y=w*a[i+j+k]%mod;
                a[i+j]=(x+y)%mod; a[i+j+k]=(x-y+mod)%mod;
            }
        }
    }
    if (id==-1) reverse(a+1,a+n);
} 
void cf(LL *f,LL *g)
{
    for (int i=0;i<n;i++) a[i]=f[i];
    for (int i=0;i<n;i++) b[i]=g[i];
    NTT(a,1); NTT(b,1);
    for (int i=0;i<=n;i++) a[i]=a[i]*b[i]%mod;
    NTT(a,-1);
    for (int i=0;i<n;i++) a[i]=a[i]*inv%mod;
    for (int i=0;i<m-1;i++) f[i]=(a[i]+a[i+m-1])%mod; 
}
void ksmNTT(int k)
{
    ans[0]=1;
    for (;k;k>>=1,cf(f,f))
      if (k&1) cf(ans,f);
}
int main()
{
    int c,x,S,s;scanf("%d%d%d%d",&c,&m,&x,&S);
    int g=yg(m);int pos;
    for (int i=1;i<=S;i++) scanf("%d",&s),vis[s]=1;
    int now=1;
    for (int i=0;i<m-1;i++,now=now*g%m)
    {
        if (vis[now]) f[i]=1;
        if (now==x) pos=i;
    }
    int L=0;
    for (n=1;n<=(m-1)*2;n<<=1) L++;
    for (int i=0;i<n;i++) r[i]=(r[i>>1]>>1)|((i&1)<<L-1);
    inv=ksm(n,mod-2,mod);ksmNTT(c); 
    printf("%lld",ans[pos]);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值