zoukankan      html  css  js  c++  java
  • [SDOI2015]序列统计

    [SDOI2015]序列统计

    标签: NTT 快速幂


    Description

    给你一个模m意义下的数集,需要用这个数集生成一个数列,使得这个数列在的乘积为x。
    问方案数模(1004535809)

    Solution

    首先很容易写出一个dp。
    (dp_{i,j})数列长度为i,乘积为j的方案数。
    这么做是(O(nm^2))的。
    所以我们肯定要搞点事情,把n变成logn。
    这个数列显然是满足结合律的,并且每次的转移都相同。
    于是可以写一个快速幂,把n降为logn。
    注意到乘积不太好维护,所以可以用原根来把乘法变成加法。
    那么现在问题就变成了,(dp_{i,j})表示数列长度为i,和为j的方案数。
    是不是很像一个背包?
    注意到每次合并两个背包就是一个卷积,所以我们用NTT维护即可。
    最终复杂度。(O(m logm logn))

    Code

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #include<stack>
    #include<set>
    #include<map>
    using namespace std;
    #define ll long long
    #define REP(i,a,b) for(int i=(a),_end_=(b);i<=_end_;i++)
    #define DREP(i,a,b) for(int i=(a),_end_=(b);i>=_end_;i--)
    #define EREP(i,a) for(int i=start[(a)];i;i=e[i].next)
    inline int read()
    {
    	int sum=0,p=1;char ch=getchar();
    	while(!(('0'<=ch && ch<='9') || ch=='-'))ch=getchar();
    	if(ch=='-')p=-1,ch=getchar();
    	while('0'<=ch && ch<='9')sum=sum*10+ch-48,ch=getchar();
    	return sum*p;
    }
    
    const int maxn=2e4+20;
    const int mod=1004535809;
    
    int yg;
    int len,m,en,n,to[maxn];
    
    inline int power(int a,int b,int mod)
    {
    	int ans=1;
    	while(b)
    	{
    		if(b & 1)ans=(ll)ans*a%mod;
    		b>>=1;
    		a=(ll)a*a%mod;
    	}
    	return ans;
    }
    
    inline void getyg()
    {
    	for(yg=2;yg<=m-1;yg++)
    	{
    		int x=m-1,y=x,flg=1;
    		for(int i=2;i*i<=y;i++)
    		{
    			if(x%i)continue;
    			while(!(y%i))y/=i;
    			if(power(yg,x/i,m)==1){flg=0;break;}
    		}
    		if(y>1)if(power(yg,x/y,m)==1)continue;
    		if(flg)return;
    	}
    }
    
    int rev[maxn],a[maxn],f[maxn],g[maxn];
    
    void NTT(int *p,int opt)
    {
    	REP(i,0,n-1)if(i<rev[i])swap(p[i],p[rev[i]]);
    	for(int i=1;i<n;i<<=1)
    	{
    		int W=power(3,(mod-1)/(i<<1),mod);
    		for(int j=0;j<n;j+=i<<1)
    		{
    			int w=1;
    			for(int k=j;k<j+i;k++,w=(ll)w*W%mod)
    			{
    				int x=p[k],y=(ll)w*p[k+i]%mod;
    				p[k]=x+y;p[k+i]=x-y;
    				if(p[k]>=mod)p[k]-=mod;
    				if(p[k+i]<0)p[k+i]+=mod;
    			}
    		}
    	}
    	if(opt==-1)
    	{
    		reverse(p+1,p+n);
    		int inv=power(n,mod-2,mod);
    		REP(i,0,n-1)p[i]=(ll)p[i]*inv%mod;
    	}
    }
    
    inline void init()
    {
    	len=read();m=read();en=read();n=read();
    	getyg();
    	REP(i,0,m-2)to[power(yg,i,m)]=i;
    	REP(i,1,n)
    	{
    		int x=read();if(!x)continue;
    		a[i]=to[x],f[a[i]]++;
    	}
    	int s=2*(m-1),l=0;
    	n=1;while(n<s)l++,n<<=1;
    	REP(i,1,n-1)rev[i]=(rev[i>>1]>>1)| ( (i & 1)<<(l-1));
    }
    
    inline void doing()
    {
    	//REP(i,0,n-1)printf("%d ",g[i]);puts("");
    	g[0]=1;
    	while(len)
    	{
    		if(len & 1)
    		{
    			NTT(g,1);
    			NTT(f,1);
    			REP(i,0,n-1)g[i]=(ll)f[i]*g[i]%mod;
    			NTT(g,-1);
    			NTT(f,-1);
    			REP(i,m-1,n-1)
    				g[i%(m-1)]=(g[i%(m-1)]+g[i])%mod,g[i]=0;
    		}
    		len>>=1;
    		NTT(f,1);
    		REP(i,0,n-1)f[i]=(ll)f[i]*f[i]%mod;
    		NTT(f,-1);
    		REP(i,m-1,n-1)
    			f[i%(m-1)]=(f[i%(m-1)]+f[i])%mod,f[i]=0;
    	}
    	printf("%d
    ",g[to[en]]);
    }
    
    int main()
    {
    	freopen("sequence.in","r",stdin);
    	freopen("sequence.out","w",stdout);
    	init();
    	doing();
    	return 0;
    }
    
    
    
  • 相关阅读:
    傻瓜教程:asp.net(c#) 如何配置authentication,完成基于表单的身份验证
    ajax与php交互的get和post两种实现方式
    php 存储过程
    一万小时天才理论
    servlet阅读
    post and get
    合并两个有序数组(重新开始)
    Java参数传递问题
    一万小时(如何实现)阅读
    java IO 流的学习(我们到底能走多远系列1)
  • 原文地址:https://www.cnblogs.com/gzy-cjoier/p/8422929.html
Copyright © 2011-2022 走看看