【题目描述】
给出n个正整数a1,a2…an和一个质数mod.一个变量x初始为1.进行m次操作.每次在n个数中随机选一个ai,然后x=x*ai%mod.问m次操作之后x的取值的期望.
答案一定可以表示成a/b的精确分数形式.a和b可能很大,所以只需要输出a*(b^(10^9+5))模10^9+7的结果.
【输入格式】
第一行三个整数n,m,mod.
接下来一行n个空格隔开的正整数a1,a2…an
【输出格式】
一行一个整数表示答案
【样例输入】
2
1 3 1 2
【样例输出】
3
【数据范围】
第1个测试点:mod=2
第2个测试点:n=1
第3,4,5个测试点:m<=1000,1<=mod<=300.
第6,7,8个测试点:1<=mod<=300
对于全部测试点: 1<=ai<mod,mod为质数1<=mod<=1000,
对于全部测试点:1<=n<=10^5,1<=m<=10^9
【孙金宁教你学数学】
质数P的原根g满足1<=rt<P,且rt的1次方,2次方…(P-1)次方在模P意义下可以取遍1到(P-1)的所有整数.
欧拉定理:对于质数P,1<=x<P的任意x的P-1次方在模P意义下都为1.
显然,原根的1次方,2次方…(P-2)次方在模P意义下都不为1,只有(P-1)次方在模P意义下为1.这也是一个数成为原根的充分必要条件.
法一。Mod = 1e9 +7;
f[i][j]:i个数a(a1,a2....am)相乘模mod得j的方案数=、=。
因为f[i+1][j*k%mod] += (f[i][j] * k的个数)%Mod。m那么大。。。数组开不下,一个一个枚举也超时!
同理可得f[2*i][j*k%mod] += (f[i][j] * f[i][k])%Mod。
考虑把m二进制拆分。也就是f[2*i][j*k%mod] = (f[i][j] * f[i][k])%Mod。//数组开不下?滚起来!!!也可以只要一维,因为2*i,只会由i影响,拿一个辅助数组,把所有i得到的2i,先存在fz中,再赋回去。
如果m的二进制的第i位为1的话,那么所有的f[i][j]肯定是取的m个数的一部分。f[i][j]和快速幂的更新方法一样=、=
f[2*i][j*k%mod] += (f[i][j] * f[i][k])%Mod。
int mpow(int a,int b)
{
int rt = 1;
for( ;b;b >>= 1, a = 1ll * a * a % modd)
{
if(b&1) rt = 1ll * a * rt %modd;
}
return rt;
}
上面是求a^b的板子,我们现在要求b个不同的a相乘。
现在m相当于b, f数组就相当于a。//这里的f[j]表示选了2^i个不同的a相乘%mod得j的方案数。
g相当于rt。//这里的g[j]表示选了2^i+(2^k) + 2^z...个不同的a(m的二进制的第i位,并且第i位为1,2^i个数是m个数的一部分,k,z同理)相乘%mod得j的方案数.
其实g[i+k+z)][j*jj%mod] += (f[i][j] * g[k+z][jj])%Mod(选了2^i个不同的a相乘%mod得j的方案数 * 选了2^(k+z)个不同的a相乘%mod得jj的方案数 ==选了2^(i+k+z)个不同的a相乘%mod得(j * jj%mod)的方案数 )
i是一位一位的把m给二进制拆分分的的。
最后的g[j]就是m个不同的数相乘%mod得j的方案数;
期望等于每个最后方案数*每个最后取值的和/总方案数。//我尽力了。。请各位感性理解一波=。=、
#include<bits/stdc++.h>
using namespace std;
const int modd = 1e9 + 7;
int n,m,mod,g[1005],fz[1005],f[1005];
void read(int &x)
{
x = 0; int f = 0; char c = getchar();
while(c < '0' || c > '9')
{
if(c == '-') f = 1; c = getchar();
}
while(c >= '0' && c <= '9')
{
x = x * 10 + c - '0'; c = getchar();
}
if(f) x = -x ;
}
int mpow(int a,int b)
{
int rt = 1;
for( ;b;b >>= 1, a = 1ll * a * a % modd)
{
if(b&1) rt = 1ll * a * rt %modd;
}
return rt;
}
void hehe()
{
for(int i = 1; i < mod ;i++) fz[i] = 0;
for(int i = 1; i < mod ;i++)
for(int j = 1; j < mod; j++)
fz[1ll * i * j % mod] = ( fz[1ll * i * j % mod] + 1ll * f[i] * f[j] % modd) % modd;
for(int i = 1; i < mod ;i ++)
f[i] = fz[i];
}
int main()
{
freopen ( "rand10.in" , "r" , stdin ) ;
freopen ( "rand.out" , "w" , stdout ) ;
read(n);read(m);read(mod);
for(int i = 1; i <= n; i++) {
int x ;read(x);f[x]++;
}
g[1] = 1; int ha = m;
for( ; ha;ha >>= 1,hehe())
{
if(ha & 1)
{
for(int i = 1; i < mod ;i++) fz[i] = 0;
for(int i = 1; i < mod ;i++)
for(int j = 1; j < mod; j++)
fz[1ll * i * j % mod] = ( fz[1ll * i * j % mod] + 1ll * g[i] * f[j] % modd) % modd;
for(int i = 1; i < mod ;i ++)
g[i] = fz[i];
}
}
int ans = 0;
for(int i = 1; i < mod; i++) ans = (ans + 1ll * i * g[i]) % modd;
printf("%d", 1ll * ans * mpow(mpow(n,m),modd-2) % modd);
return 0;
}
法二:原根rt的1次方,2次方…(P-1)次方在模P意义下可以取遍1到(P-1)的所有整数。所以不论是多少个a1,a2,a3,a5....an相乘。
都可以用rt表示。
f[i][j] :2^i个数相乘最后模mod得rt^j次方的方案数。当幂的次数(j)大于mod-1(p - 1)时,就从头来.
f[i][(j+k)%(mod-1)] += f[i-1][k] *f[i-1][j];