bzoj3992: [SDOI2015]序列统计

链接

  http://www.lydsy.com/JudgeOnline/problem.php?id=3992

题解

  不好做不好做,真心不好做。
  这个题显然是dp,一开始我们这样设计状态 f[i][j] 表示长度为i的序列,积在模 M 意义下等于j的数列的方案数。
  那显然直接做的复杂度是 O(NM2) ,到了这里,有一种矩阵快速幂做法,可以直接优化到 O(NM3) ,应该能过30%。
  显然正解不允许带有 O(M3) ,所以矩阵肯定是不行了。
  到这里可能就需要原根了,求出原根,每个数都能表示成原根的幂,这样就可以用指数代替数, f[i][j] 表示长度为 i ,乘积在模M意义下等于原根的 j 次幂的方案数。
  这样转移就成了f[i][j]f[i][j+k mod M1],显然这就是多项式卷积,把 f[i] 看做一个多项式,它的 j 次项乘以另一个多项式上k次项上的一个1,这个乘积会加到结果的第 j+k 次项上去。
  很蛋疼的地方在于取模,不取模还好,取模怎么做。其实也没啥,每次求完卷积之后肯定会多出来一些不合法的,即它们的下标大于 M1 ,这个时候只需要把的值加到合法的位置就好了,意思就是 f[i modM1] f[i]
  显然多项式卷积满足交换律,所以只需要把用来转移的那个多项式进行快速幂。
  总的时间复杂度 O(MlogMlogN)

代码

//DP+NTT
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 131072
#define mod 1004535809ll
#define ll long long
using namespace std;
ll up, N, M, S[maxn], R[maxn], trans[maxn], f[maxn], inv, tmp[maxn], ans[maxn],
    tmp1[maxn], tmp2[maxn], X, pr;
inline ll pow(ll a, ll b, ll p)
{
    ll t=a, ans=1;
    for(;b;b>>=1,t=t*t%p)if(b&1)ans=ans*t%p;
    return ans;
}
void ntt(ll *a, ll n, ll opt)
{
    ll i, j, k, p, w, wn, x, y;
    for(i=0;i<n;i++)if(i<R[i])swap(a[i],a[R[i]]);
    for(i=1;i<n;i<<=1)
    {
        wn=pow(3,(mod-1)/(i<<1),mod);
        for(p=i<<1,j=0;j<n;j+=p)
            for(w=1,k=0;k<i;k++,w=w*wn%mod)
            {
                x=a[k+j], y=a[k+j+i]*w;
                a[k+j]=(x+y)%mod, a[k+j+i]=(x-y+mod)%mod;
            }
    }
    if(opt==-1){reverse(a+1,a+n);for(i=0;i<n;i++)a[i]=(a[i]*inv)%mod;}
}
inline void juan(ll *a, ll *b, ll *c)
{
    ll i;
    for(i=0;i<up;i++)tmp1[i]=a[i], tmp2[i]=b[i];
    ntt(tmp1,up,1); ntt(tmp2,up,1);
    for(i=0;i<up;i++)c[i]=tmp1[i]*tmp2[i];
    ntt(c,up,-1);
    for(i=0;i<up;i++)c[i]=(c[i]+mod)%mod;
    for(ll i=M-1;i<up;i++)c[i%(M-1)]+=c[i], c[i]=0;
}
inline void pow(ll *a, ll b)
{
    for(ll i=0;i<up;i++)tmp[i]=a[i], ans[i]=0; ans[0]=1;
    for(;b;b>>=1,juan(tmp,tmp,tmp))if(b&1)juan(ans,tmp,ans);
    for(ll i=0;i<up;i++)a[i]=ans[i];
}
inline ll PR(ll x)
{
    ll i, j;
    for(i=2;i<x;i++)
    {
        for(j=1;j<=x-2;j++)if(pow(i,j,x)==1)break;
        if(j==x-1 and pow(i,x-1,x)==1)return i;
    }
}
void init()
{
    ll i, L=0, t;
    scanf("%lld%lld%lld%lld",&N,&M,&X,S);
    for(i=1;i<=*S;i++)scanf("%lld",&t),t?S[t]=1:0;
    for(up=1;up<=(M-2)<<1;up<<=1)L++;
    for(i=0;i<up;i++)R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    pr=PR(M);
    for(i=0;i<=M-2;i++)if(S[pow(pr,i,M)])trans[i]=1;
    inv=pow(up,mod-2,mod);
}
void work()
{
    ll i;
    f[0]=1;
    pow(trans,N);
    juan(f,trans,f);
    for(i=0;i<=M-2;i++)if(pow(pr,i,M)==X){printf("%lld",f[i]);break;}
}
int main()
{
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值