这次考试嗅到了达哥的杀气。。。这是第一次带题的考试总结,毕竟达哥的香还是要上一柱的。。。
首先做一下总结,这次考试T1有判的10分没有拿到,T2有高斯消元板子打错只有40分,扔掉20,然后 T3的最后25分不会,实在没想到三个组合数中间加一个DP。。。推了半天式子也没头绪。。。反正能拿的分基本都拿到了10+40+75=125。。。看起来我还是太辣鸡了,整场考试一半分都拿不了。。
然后是题目,这次提高还是很大的。。。
T1:再次申明一个点期望的乘积不是乘积的期望。。。粘一发百度
我们能接触到的期望DP中可以转移,当期望结果可以相加减的时候才成立,比如买可乐,等概率赠一种龙珠,集齐7颗不同的召唤神龙,问你期望次数,显然次数是加减,如果用定义来求$p(1)*1+p(2)*2+\cdots$不用多说,我们用DP来求的话,集齐x颗的期望次数$p(1)*1+p(2)*2+\cdots$加在此基础上加某一颗的次数和概率的$\frac{7-x}{7}*(p(1)*1+p(2)*2+\cdots)$,就能转移到集齐x颗的期望次数,能转移的根源就是乘法分配律,这样就能在最后的状态也保留$p(1)*1+p(2)*2+\cdots$的形式,那在这个题中我们搞每一次操作的期望不能通过上一次的期望值乘上这一次的期望值得到,鬼知道你的概率成了几次方。那么遇到这种题怎么办呢?把概率和期望值分开,最后乘一次计算答案,$f(i,j)$表示第i次操作得到j的概率,那么转移显然可以最外层把次数作为阶段,然后把当前状态的j值枚举,再枚举序列中的数,转移稍简单,当然我们发现序列远长于mod,而我们不关注序列,序列中值域又在1~mod-1之间,那么直接把枚举序列的数变成枚举序列的值域,转移一样简单。那么这个题其实到这就是我的范围了,在往后的倍增优化还没接触过,什么原根循环矩阵更是听都没听过。。。然后我们稍看一下,其实这种不算小数的期望一般都是骗人的,还没乘逆元的时候就是方案数,那么我们最后再乘,$f[1][]$这个数组观察一下发现它其实就是序列里数出现的个数,那么这样我们改变一下定义,$f(i,j)$表示i个数相乘的j的概率,其实这种放回的选择,8次1个和1次8个是一样的,因为假设一次可以选重的,那么这m次就可以不用一次一次加过来,思路逐渐清奇。。。。还记得蒙哥马利高效算法不,好吧就是快速幂,它就是从1次1次乘变成1坨1坨乘,那么这个题就可以解决了,和矩阵乘法优化的思路一样,通过减少低效的乘法直接把次数log掉,刚刚那个8次1个等效于1次8个就是满足结合律。
#include<cstring> #include<iostream> #include<cstdio> using namespace std; const int MOD=1e9+7,N=1e5+20,MD=1020; int a[N],f[MD],tmp[MD],g[MD],w[MD]; int rd() { char cc=getchar(); int s=0,w=1; while(cc<'0'||cc>'9') {if(cc=='-')w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } int qpow(int a,int k,int mod) { int ans=1; for(;k;k>>=1,a=1ll*a*a%mod) if(k&1) ans=1ll*ans*a%mod; return ans; } void pow(int mod) { memset(tmp,0,sizeof(tmp)); for(int j=0;j<mod;j++) for(int k=0;k<mod;k++) tmp[j*k%mod]=(tmp[j*k%mod]+1ll*f[j]*f[k]%MOD)%MOD; memcpy(f,tmp,sizeof(tmp)); } int main() { int n=rd(),m=rd(),mod=rd(),p; long long s=0,ans=0,sum; for(int i=1;i<=n;i++) f[rd()]++; int inv_n=qpow(n,m,MOD),k=m,op=1; inv_n=qpow(inv_n,MOD-2,MOD); g[1]=1; for(;k;k>>=1) { if(k&1) { memset(tmp,0,sizeof(tmp)); for(int j=0;j<mod;j++) for(int k=0;k<mod;k++) tmp[j*k%mod]=(tmp[j*k%mod]+1ll*g[j]*f[k]%MOD)%MOD; memcpy(g,tmp,sizeof(tmp)); } pow(mod); } for(int j=0;j<mod;j++)ans=(ans+1ll*j*g[j]%MOD)%MOD; ans=1ll*ans*inv_n%MOD; printf("%lld\n",ans); } /* g++ 1.cpp -o 1 ./1 3 5 100 1 2 3 */