[2018湖南省队集训] 6.28 T2 color
毒瘤计数题2333,(小声)k其实可以出到1e9,不过这样求组合数的时候就要记1000种数的1~1000次下降幂(用到的组合数中第一维在1e9级别的只有1000种左右,第二维都是<=1000),写起来可能比较麻烦。。。。不过既然k只有1e6我们何必要这么毒瘤呢233333
考虑什么样的棋盘是合法的。
发现只有中间的棋盘出现的颜色集合是 最边上两列棋盘出现颜色集合 的交集 的子集 的时候,才合法(考虑扫描线向右移动一列)。
于是我们可以先枚举 交集大小,再枚举边上两列的总颜色数,然后就可以直接用组合数+斯特林数 乘乘乘乘.... 算啦。
以下有几个地方可以优化很大的常数:
1.只和交集大小和k(或者其他常量)有关的组合数或者其他东西,可以在算完总颜色数的答案之和之和再乘上,将减少大量的乘法运算。。。
2.有两个组合数连乘的地方推一推式子发现有一对分子分母可以消去一个阶乘,常数*= (2/3) [然并卵]
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #define ll long long using namespace std; const int N=1005,ha=1e9+7; inline int add(int x,int y){ x+=y; return x>=ha?x-ha:x;} inline void ADD(int &x,int y){ x+=y; if(x>=ha) x-=ha;} int ksm(int x,int y){ int an=1; for(;y;y>>=1,x=x*(ll)x%ha) if(y&1) an=an*(ll)x%ha; return an; } inline int sq(int x){ return x*(ll)x%ha;} int jc[N*N],ni[N*N],n,m,T,k,ans,S[N][N],num; inline int C(int x,int y){ return jc[x]*(ll)ni[y]%ha*(ll)ni[x-y]%ha;} inline void init(){ jc[0]=1; for(int i=1;i<=1e6;i++) jc[i]=jc[i-1]*(ll)i%ha; ni[1000000]=ksm(jc[1000000],ha-2); for(int i=1e6;i;i--) ni[i-1]=ni[i]*(ll)i%ha; S[0][0]=1; for(int i=1;i<=1000;i++) for(int j=1;j<=i;j++) S[i][j]=add(S[i-1][j-1],S[i-1][j]*(ll)j%ha); } inline void solve(){ if(m==1){ for(int i=1;i<=n;i++) ADD(ans,C(k,i)*(ll)S[n][i]%ha*(ll)jc[i]%ha); return; } num=n*(m-2); for(int i=0,tmp=0;i<=n;tmp=0,i++){ for(int j=0,u,v;(v=(j<<1)+i)<=k&&(u=i+j)<=n;j++) ADD(tmp,sq(ni[j])%ha*(ll)ni[k-v]%ha*(ll)sq(S[n][u]*(ll)jc[u]%ha)%ha); ADD(ans,tmp*(ll)C(k,i)%ha*(ll)ksm(i,num)%ha*(ll)jc[k-i]%ha); } } int main(){ freopen("color.in","r",stdin); freopen("color.out","w",stdout); init(); scanf("%d",&T); while(T--){ ans=0,scanf("%d%d%d",&n,&m,&k); solve(),printf("%d\n",ans); } return 0; }
我爱学习,学习使我快乐