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

    Description

    link

    给定一个集合 (s),和整数 (x,n,m)(nle 10^9 mle 8000 x le m-1)

    保证 (forall ain {S},ale m)

    每次从集合里面随机选数,一共选 (n) 次,求选出来的数的乘积 (equiv x (mod m)) 的方案数

    Solution

    上手一个比较暴力的 (dp)

    (f_{i,j}) 是长度为 (i) 的序列,乘积模数为 (j) 的方案数

    转移枚举每个可以被随机出来的数转移就好了

    复杂度显然爆炸

    $N le 10^9 ? $ 矩阵乘法?

    然而并不会……


    有一个比较好的优化:

    [f_{2 imes i,p}=sum_{x imes yequiv p(mod m)} f_{i,x} imes f_{i,y} ]

    乘法取模并不能计算,怎么把乘法变加法?

    (log) 用原根,即:

    [f_{2 imes i,log_{g} p}=sum_{log_{g}x \%m+log _ {g} y\%m=log_{g} p\% m } f_{i,log_{g}x} imes f_{i,log_{g}y} ]

    直接 (NTT) 卷上 (log (n)) 次即可

    Code

    #include<bits/stdc++.h>
    using namespace std;
    //#define int long long
    namespace yspm{
    	inline int read()
    	{
    		int res=0,f=1; char k;
    		while(!isdigit(k=getchar())) if(k=='-') f=-1;
    		while(isdigit(k)) res=res*10+k-'0',k=getchar();
    		return res*f;
    	}
    	const int mod=1004535809,N=50010,L=8010;
    	int g,d[100],cnt,n,m,x,sz,s[L],id[L];
    	inline int ksm1(int x,int y,int mod)
    	{
    		int res=1; for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) res=1ll*res*x%mod;
    		return res;
    	}
    	inline int ksm(int x,int y)
    	{
    		int res=1; for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) res=1ll*res*x%mod;
    		return res;
    	}
    	inline bool check(int x,int p)
    	{
    		for(int i=1;i<=cnt;++i)
    		{
    			if(ksm1(x,(p-1)/d[i],p)==1) return 0;
    		} return 1;
    	}
    	inline void find(int x)
    	{	
    		int t=x-1; cnt=0;
    		for(int i=2;i*i<=t;++i) 
    		{
    			if(t%i==0) 
    			{
    				d[++cnt]=i; 
    				while(t%i==0) t/=i;
    			}
    		} if(t>1) d[++cnt]=t;
    		g=2; while(1) if(check(g,x)) break; else ++g;
    		return ;
    	}
    	int A[N],r[N],B[N];
    	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
    	inline int del(int x,int y){return x-y>=0?x-y:x-y+mod;}
    	inline void NTT(int *f,int n,int opt)
    	{
    		for(int i=0;i<n;++i) if(i<r[i]) swap(f[i],f[r[i]]);
    		for(int p=2;p<=n;p<<=1)
    		{
    			int len=p>>1,tmp=ksm(g,(mod-1)/p); 
    			if(opt==-1) tmp=ksm(tmp,mod-2);
    			for(int k=0;k<n;k+=p)
    			{
    				int buf=1;
    				for(int l=k;l<k+len;++l)
    				{
    					int tt=1ll*buf*f[l+len]%mod;
    					f[l+len]=del(f[l],tt);
    					f[l]=add(f[l],tt);
    					buf=1ll*buf*tmp%mod;
    				}
    			}
    		} 
    		if(opt==-1) for(int i=0;i<n;++i) f[i]=1ll*f[i]*ksm(n,mod-2)%mod;
    		return ;
    	}
    	int len,sum,t[N];
    	inline void mul(int *a,int *b,int *c)
    	{	
    		static int A[N],B[N],t[N];
    		for(int i=0;i<len;++i) A[i]=a[i],B[i]=b[i];
    		NTT(A,len,1); NTT(B,len,1);   
    		for(int i=0;i<len;++i) A[i]=1ll*A[i]*B[i]%mod;  	  
    		NTT(A,len,-1);
    		for(int i=0;i<m-1;++i) t[i]=add(A[i],A[i+m-1]);
    		for(int i=0;i<m-1;++i) c[i]=t[i];
    		return ;
    	}
    	signed main()
    	{
    		n=read(); m=read(); x=read(); sz=read();  find(m); 
    		for(int i=0;i<m-1;++i) id[ksm1(g,i,m)]=i; g=3;
    		for(int i=1;i<=sz;++i) s[i]=read(),B[id[s[i]%m]]+=s[i]%m?1:0;
    		len=1; sum=m<<1; while(len<=sum) len<<=1; 
    		for(int i=0;i<len;++i) r[i]=r[i>>1]>>1|((i&1)?len>>1:0);
    		
    		A[id[1]]=1; 
    		for(;n;n>>=1,mul(B,B,B))
    		{
    			if(n&1) mul(A,B,A);
    		} 
    		printf("%d
    ",A[id[x%m]]);
    		return 0;
    	}
    }
    signed main(){return yspm::main();}
    ```**------------恢复内容开始------------**
    # Description
    
    [link](https://www.luogu.com.cn/problem/P3321)
    
    给定一个集合 $s$,和整数 $x,n,m$ ($nle 10^9   mle 8000   x le m-1$)
    
    保证 $forall ain {S},ale m$
    
    每次从集合里面随机选数,一共选 $n$ 次,求选出来的数的乘积 $equiv x  (mod m)$ 的方案数
     
    # Solution
    
    上手一个比较暴力的 $dp$ 
    
    设 $f_{i,j}$ 是长度为 $i$ 的序列,乘积模数为 $j$ 的方案数
    
    转移枚举每个可以被随机出来的数转移就好了
    
    复杂度显然爆炸
    
    $N le 10^9   ?  $ 矩阵乘法?
    
    然而并不会……
    
    ----
    
    有一个比较好的优化:
    
    $$f_{2	imes i,p}=sum_{x	imes yequiv p(mod  m)} f_{i,x}	imes f_{i,y}$$
    
    乘法取模并不能计算,怎么把乘法变加法?
    
    套 $log$ 用原根,即:
    
    $$f_{2	imes i,log_{g} p}=sum_{log_{g}x \%m+log _ {g} y\%m=log_{g} p\% m } f_{i,log_{g}x}	imes f_{i,log_{g}y}$$
    
    直接 $NTT$ 卷上 $log (n)$ 次即可
    
    # Code
    
    ```cpp
    #include<bits/stdc++.h>
    using namespace std;
    //#define int long long
    namespace yspm{
    	inline int read()
    	{
    		int res=0,f=1; char k;
    		while(!isdigit(k=getchar())) if(k=='-') f=-1;
    		while(isdigit(k)) res=res*10+k-'0',k=getchar();
    		return res*f;
    	}
    	const int mod=1004535809,N=50010,L=8010;
    	int g,d[100],cnt,n,m,x,sz,s[L],id[L];
    	inline int ksm1(int x,int y,int mod)
    	{
    		int res=1; for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) res=1ll*res*x%mod;
    		return res;
    	}
    	inline int ksm(int x,int y)
    	{
    		int res=1; for(;y;y>>=1,x=1ll*x*x%mod) if(y&1) res=1ll*res*x%mod;
    		return res;
    	}
    	inline bool check(int x,int p)
    	{
    		for(int i=1;i<=cnt;++i)
    		{
    			if(ksm1(x,(p-1)/d[i],p)==1) return 0;
    		} return 1;
    	}
    	inline void find(int x)
    	{	
    		int t=x-1; cnt=0;
    		for(int i=2;i*i<=t;++i) 
    		{
    			if(t%i==0) 
    			{
    				d[++cnt]=i; 
    				while(t%i==0) t/=i;
    			}
    		} if(t>1) d[++cnt]=t;
    		g=2; while(1) if(check(g,x)) break; else ++g;
    		return ;
    	}
    	int A[N],r[N],B[N];
    	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
    	inline int del(int x,int y){return x-y>=0?x-y:x-y+mod;}
    	inline void NTT(int *f,int n,int opt)
    	{
    		for(int i=0;i<n;++i) if(i<r[i]) swap(f[i],f[r[i]]);
    		for(int p=2;p<=n;p<<=1)
    		{
    			int len=p>>1,tmp=ksm(g,(mod-1)/p); 
    			if(opt==-1) tmp=ksm(tmp,mod-2);
    			for(int k=0;k<n;k+=p)
    			{
    				int buf=1;
    				for(int l=k;l<k+len;++l)
    				{
    					int tt=1ll*buf*f[l+len]%mod;
    					f[l+len]=del(f[l],tt);
    					f[l]=add(f[l],tt);
    					buf=1ll*buf*tmp%mod;
    				}
    			}
    		} 
    		if(opt==-1) for(int i=0;i<n;++i) f[i]=1ll*f[i]*ksm(n,mod-2)%mod;
    		return ;
    	}
    	int len,sum,t[N];
    	inline void mul(int *a,int *b,int *c)
    	{	
    		static int A[N],B[N],t[N];
    		for(int i=0;i<len;++i) A[i]=a[i],B[i]=b[i];
    		NTT(A,len,1); NTT(B,len,1);   
    		for(int i=0;i<len;++i) A[i]=1ll*A[i]*B[i]%mod;  	  
    		NTT(A,len,-1);
    		for(int i=0;i<m-1;++i) t[i]=add(A[i],A[i+m-1]);
    		for(int i=0;i<m-1;++i) c[i]=t[i];
    		return ;
    	}
    	signed main()
    	{
    		n=read(); m=read(); x=read(); sz=read();  find(m); 
    		for(int i=0;i<m-1;++i) id[ksm1(g,i,m)]=i; g=3;
    		for(int i=1;i<=sz;++i) s[i]=read(),B[id[s[i]%m]]+=s[i]%m?1:0;
    		len=1; sum=m<<1; while(len<=sum) len<<=1; 
    		for(int i=0;i<len;++i) r[i]=r[i>>1]>>1|((i&1)?len>>1:0);
    		
    		A[id[1]]=1; 
    		for(;n;n>>=1,mul(B,B,B))
    		{
    			if(n&1) mul(A,B,A);
    		} 
    		printf("%d
    ",A[id[x%m]]);
    		return 0;
    	}
    }
    signed main(){return yspm::main();}
    

    ------------恢复内容结束------------

  • 相关阅读:
    b_lg_选学霸(并查集+01背包)
    b_lq_小明的魔法(反向思维)
    多测师课堂019_第一个月综合面试题(01) _高级讲师肖sir
    多测师课堂017_项目数据 _高级讲师肖sir
    linux whoami 显示当前用户的用户名
    linux shutdown 命令 关机 重启
    linux reboot 重启命令
    linux基础 目录
    linux uname 命令 打印系统信息
    前端 CSS层叠性 CSS选择器优先级
  • 原文地址:https://www.cnblogs.com/yspm/p/13401693.html
Copyright © 2011-2022 走看看