J. 放棋子
分析:
-
D P DP DP + + + 组合数学
-
刚开始想着去推数学公式,推半天,推的乱七八糟的
但是,还是发现了关键的一点:第 i i i 列的棋子数一定等于 i % n i\%n i%n 列的棋子数
考虑第n+1列的棋子数,是第二个区域的最后一列,它与第一个区域有n-1列是共有的
为了保证,第二个区域和第一个区域一样都是k个棋子,则,第n+1列的棋子数必定与第一列的相等
以此类推,便可发现上述结论
-
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 列放 j j j 个棋子的方案数
d p [ i ] [ j ] = ∑ l = 0 m i n ( n , j ) d p [ i − 1 ] [ j − l ] ∗ g [ l ] [ i < = y u ] 当 前 状 态 ( 前 i 列 ) 是 由 前 i − 1 列 的 所 有 情 况 转 移 过 去 的 dp[i][j]=\sum_{l=0}^{min(n,j)}dp[i-1][j-l]*g[l][i<=yu] \\ 当前状态 (前i列) 是由前i-1列的所有情况转移过去的 dp[i][j]=l=0∑min(n,j)dp[i−1][j−l]∗g[l][i<=yu]当前状态(前i列)是由前i−1列的所有情况转移过去的
详见代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mo=1e9+7,N=105;
int c[N][N];
void init()
{
c[0][0]=1;
for(int i=1;i<=100;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)
{
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mo;
}
}
}
int ksm(int a,int b,int p=mo)
{
int ans=1;
while(b)
{
if(b&1) ans=ans*a%p;
a=a*a%p; b>>=1;
}
return ans;
}
int g[N][3],f[N][N*N];
signed main()
{
init();
int n,m,k;
cin>>n>>m>>k;
int d=m/n,y=m%n; // d表示循环了d轮,y表示多余的 y (即yu) 轮,多循环了一次
for(int i=0;i<=n;i++)
{
g[i][0]=ksm(c[n][i],d);
g[i][1]=ksm(c[n][i],d+1);
// 预处理单个列的各种放法下的,经过d轮循环后的贡献值
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
for(int l=0;l<=min(n,j);l++)
{
f[i][j]=(f[i][j]+f[i-1][j-l]*g[l][i<=y]%mo)%mo;
}
}
}
cout<<f[n][k]<<endl;
}