1. 程式人生 > >【組合數學】【DP】三校聯考 10.15 —— Chess

【組合數學】【DP】三校聯考 10.15 —— Chess

題目描述

dirty 在一個棋盤上放起了棋子。
棋盤規格為 n ∗ m,他希望任意一個 n ∗ n 的區域內都有 K 個棋子。dirty 很快就放置好了一
個滿足條件的棋盤方案,但是他認為這樣過於簡單了,他希望知道有多少個滿足條件的方案。

看題第一眼即可知道是關於組合數學的.
通過觀察發現,如果棋盤上前n列已經定了,那麼之後的情況只會是前面n列的迴圈.因此只需考慮n列.
可以得出以下式子
sum=Ck1nCk2nCk3nCknnsum=C_{k1}^{n} \cdot C_{k2}^{n} \cdot C_{k3}^{n} \cdots C_{kn}^{n}

Ck3nCknn
其中K=k1+k2++knK=k1+k2+\cdots+kn
這個sumsum的含義是在每一列的棋子數固定的情況下前n列的方案數.
那麼這種情況對於答案的貢獻為
summnCk1nCk2nCk3nCkmmodnnsum^{\lfloor\frac{m}{n}\rfloor}\cdot C_{k1}^{n} \cdot C_{k2}^{n} \cdot C_{k3}^{n} \cdots C_{k\ _{m \ mod\ n}}^{n}
然後將所有的情況利用dp統計.(詳見程式碼)
題目時間限制比較卡,注意預處理出組合數.

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int MOD=1e9+7;
const int MAXN=105;
const int MAXM=10005;

int n,c;
ll m,x1,x2;
ll fac[MAXM],finv[MAXM];
ll d[MAXN][MAXM],f[2][MAXN];

void Init(){
    fac[0]=1;
    for(int i=1;i<MAXM;i++) fac[i]=fac[i-
1]*i%MOD; finv[1]=1; for(int i=2;i<MAXM;i++) finv[i]=(MOD-MOD/i)*finv[MOD%i]%MOD; finv[0]=1; for(int i=1;i<MAXM;i++) finv[i]=finv[i-1]*finv[i]%MOD; } ll C(ll x,ll y){ if(x<y) return 0; return (fac[x]*finv[y]%MOD)*finv[x-y]%MOD; } ll ksm(ll x,ll y){ y%=(MOD-1); ll res=1; while(y){ if(y&1) res=res*x%MOD; x=x*x%MOD; y>>=1ll; } return res; } int main(){ freopen("chess.in","r",stdin); freopen("chess.out","w",stdout); Init(); scanf("%d %lld %d",&n,&m,&c); if(m<n) return printf("%lld",ksm(2,n*m)),0; x1=m/n,x2=m%n; d[n+1][0]=1; for(int i=0;i<=n;i++) f[0][i]=ksm(C(n,i),x1),f[1][i]=ksm(C(n,i),x1+1); for(int i=n;i>=1;i--) for(int j=c;j>=0;j--){ for(int k=0;k<=min(n,j);k++) d[i][j]=(d[i][j]+d[i+1][j-k]*(i<=x2?f[1][k]:f[0][k])%MOD)%MOD; if(i==1) break; } printf("%lld\n",d[1][c]); fclose(stdin); fclose(stdout); }