HZOJ 随

这个题的题解并不想写……一个写的很详细的blog

第1个测试点:mod=2,a[i]<mod(仔细看题),则n个数字都是1,直接输出1即可.

第2个测试点:每次乘上去的数字只有一种选择,快速幂即可.

第3,4,5个测试点:定义f[i][j]表示i次操作后x的数值为j的概率.直接转移,复杂度O(m*mod^2)

1         f[0][1]=1;
2         for(int i=0;i<m;i++)
3             for(int j=0;j<mod;j++)
4                 for(int k=0;k<mod;k++)//这里不要枚举n啊,可以先处理出来每个数出现的概率
5                     f[i+1][j*k%mod]=(f[i+1][j*k%mod]+f[i][j]*v[k]%p)%p;    

第6,7,8个测试点:第3,4,5个测试点中的DP转移可以转化为矩阵乘法形式,利用矩阵快速幂进行优化,复杂度O(mod^3*logm)矩阵快速幂优化dp见--->这里

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<cmath>
 5 #define int LL
 6 #define MAXN 100010
 7 #define LL long long
 8 using namespace std;
 9 struct jz
10 {
11     LL m[310][310];
12     void clear(){memset(m,0,sizeof(m));}
13 }base;
14 int n,m,mod,a[MAXN];
15 const int p=1e9+7;
16 jz operator * (jz &a,jz &b)
17 {
18     jz ans;ans.clear();
19     for(int i=0;i<mod;i++)
20         for(int j=0;j<mod;j++)    
21             for(int k=0;k<mod;k++)
22                 ans.m[i][j]=(ans.m[i][j]+a.m[i][k]*b.m[k][j]);
23     for(int i=0;i<mod;i++)
24         for(int j=0;j<mod;j++)    
25             ans.m[i][j]%=p;
26     return ans;
27 }
28 jz operator ^ (jz a,int b)
29 {
30     jz ans=a;b--;
31     while(b)
32     {
33         if(b&1)ans=ans*a;
34         a=a*a;
35         b=b>>1;
36     }
37     return ans;
38 }
39 LL f[1010][310],tem;
40 LL v[310];
41 LL poww(LL a,int b,int mod)
42 {    
43     LL ans=1;
44     while(b)
45     {
46         if(b&1)ans=ans*a%mod;
47         a=a*a%mod;
48         b=b>>1;
49     }
50     return ans;
51 }
52 signed main()
53 {
54 //    freopen("in.txt","r",stdin);
55 //    freopen("0.out","w",stdout);
56 
57     cin>>n>>m>>mod;tem=poww(n,p-2,p);
58     for(int i=1;i<=n;i++)cin>>a[i],v[a[i]%mod]=(v[a[i]%mod]+tem)%p;
59     if(mod==2){puts("1");return 0;}
60     if(n==1){printf("%lld\n",poww(a[1],m,mod));return 0;}
61     if(m<=1000)
62     {
63         f[0][1]=1;
64         for(int i=0;i<m;i++)
65             for(int j=0;j<mod;j++)
66                 for(int k=0;k<mod;k++)        
67                     f[i+1][j*k%mod]=(f[i+1][j*k%mod]+f[i][j]*v[k]%p)%p;        
68         LL ans=0;
69         for(int i=0;i<mod;i++)
70             ans=(ans+f[m][i]*i%p)%p;
71         printf("%lld\n",ans%p);
72     }
73     else if(mod<=300)
74     {
75         for(int j=0;j<mod;j++)
76             for(int k=0;k<mod;k++)
77                 base.m[j*k%mod][j]=(base.m[j*k%mod][j]+v[k])%p;
78         base=base^m;
79         LL ans=0;
80         for(int i=0;i<mod;i++)
81             ans=(ans+base.m[i][1]*i%p)%p;
82         printf("%lld\n",ans%p);
83     }
84 }
这里放出暴力dp和矩阵优化dp

第9,10个测试点(标算):上面那个blog讲的非常详细了(然而我并没有看懂……),总感觉这个矩阵优化dp怪怪的……

先把达哥题解放出来:

利用原根进行转化,则乘法转化为加法,f[i][j]表示i次操作后x取模后等于原根的j次方的概率.指数需要对(mod-1)取模.这样转化一下我们发现转移还是矩阵的形式,而且是循环矩阵的形式.循环矩阵快速幂,复杂度O(mod^2*logm)

然后是另一种标算(本人写的这个):

定义f[i][j]表示i次操作后变成原根的j次方的概率.求出p[i][j]表示2^i次操作后变成原根的j次方的概率.倍增的思想求出f[m][]这个数组.也是O(mod^2*logm)

1     for(int i=0;i<=mod-2;i++)p[0][i]=tem*cnt[i]%mp;//乘2^0为rt^i的概率,tem为分母的逆元,cnt为个数。
2     for(int i=1;i<=32;i++)
3         for(int j=0;j<=mod-2;j++)
4             for(int k=0;k<=mod-2;k++)
5                 p[i][(j+k)%(mod-1)]=(p[i][(j+k)%(mod-1)]+p[i-1][j]*p[i-1][k]%mp)%mp;

然后用类似快速幂的方法就可以求出了,避免了直接理解矩阵。

 1 #include<iostream>
 2 #include<cstdio>
 3 #define MAXN 400010
 4 #define LL long long
 5 #define int LL
 6 using namespace std;
 7 const int mp=1e9+7;
 8 int n,m,mod,a[MAXN];
 9 int xp[3100],cnt[3100];
10 int rt;
11 int p[34][1005],f[2][1005];
12 LL poww(LL a,int b,int mod)
13 {    
14     LL ans=1;
15     while(b)
16     {
17         if(b&1)ans=ans*a%mod;
18         a=a*a%mod;
19         b=b>>1;
20     }
21     return ans;
22 }
23 void findroot()
24 {
25     for(rt=2;rt<mod;rt++)
26     {
27         bool pd=0;
28         for(int i=1;i<mod-2;i++)
29             if(poww(rt,i,mod)==1){pd=1;break;}
30         if(pd||poww(rt,mod-1,mod)!=1)continue;
31         else break;
32     }
33 }
34 signed main()
35 {
36     cin>>n>>m>>mod;
37     findroot();int tem=poww(n,mp-2,mp),x;
38     xp[0]=1;for(int i=1;i<=mod;i++)xp[i]=xp[i-1]*rt%mod;
39     for(int i=0;i<=mod-2;i++)a[xp[i]]=i;
40     for(int i=1;i<=n;i++)cin>>x,cnt[a[x]]++;
41     for(int i=0;i<=mod-2;i++)p[0][i]=tem*cnt[i]%mp;//乘2^0为rt^i的概率;
42     for(int i=1;i<=32;i++)
43         for(int j=0;j<=mod-2;j++)
44             for(int k=0;k<=mod-2;k++)
45                 p[i][(j+k)%(mod-1)]=(p[i][(j+k)%(mod-1)]+p[i-1][j]*p[i-1][k]%mp)%mp;
46     f[0][0]=1;
47     for(int k=0;m;m/=2,k++)
48     if(m&1)
49     {
50         for(int i=0;i<=mod-2;i++)f[1][i]=0;
51         for(int i=0;i<=mod-2;i++)
52             for(int j=0;j<=mod-2;j++)
53                 f[1][(j+i)%(mod-1)]=(f[1][(j+i)%(mod-1)]+f[0][i]*p[k][j]%mp)%mp;
54         for(int i=0;i<=mod-2;i++)f[0][i]=f[1][i];
55     }
56     LL ans=0;
57     for(int i=0;i<=mod-2;i++)ans=(ans+f[0][i]*xp[i]%mp)%mp;
58     printf("%lld\n",ans);
59 }
完整代码

更加优越的算法(???):本质上我们要做的是循环卷积,可以使用fft.但本题的模数使得fft较为不方便..

 

 

posted @ 2019-07-28 21:15  Al_Ca  阅读(156)  评论(0编辑  收藏  举报
ヾ(≧O≦)〃嗷~