题目
假设有一个
n∗m
n
∗
m
的棋盘,除左上角的格子的数字为
0
0
外,每一个格子的数字为中的任意一个整数。
每次走右边或者下面数字较大的那个格,如果数字一样大,则走右边。
问走过的格子上的数字之和为
S
S
的棋盘有多少种。
题解
对于的,直接DP,设
f[i][j][k]
f
[
i
]
[
j
]
[
k
]
表示走到
(i,j)
(
i
,
j
)
,和为
k
k
的棋盘有多少个?这样子不好转移,因为考虑到有些格子对你走的路径没有影响。
那么只用记录那些确定了的方案数。
则
l l 是接下来和增加的量。
最下或最右的每个格子对答案的总贡献为
n,m≤2500 n , m ≤ 2500 怎么做?
由于向右走和向下走是互不干扰的,所以可以分开搞。
f[i][j] f [ i ] [ j ] 表示向下走 i i 步,和为的方案数。
g[i][j] g [ i ] [ j ] 表示向右走 i i 步,和为的方案数。
然后卷一下就能够得到走到每个 (i,m) ( i , m ) 或 (n,i) ( n , i ) 的方案数。
枚举总和,还有向右移的和。
时间复杂度 O(n∗S2) O ( n ∗ S 2 ) 。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 3005
#define M 105
#define mo 10007
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int i,j,k,l,n,m,S,ans,temp,t,t1;
int f[N][M],g[N][M];
int x,y,z;
int jc[N+M],ny[N+M];
void add(int &x,int y){x=x+y>=mo?x+y-mo:x+y;}
int ksm(int x,int y){
int rs=1;
for(;y;y>>=1,x=(x*x)%mo)if(y&1)rs=(rs*x)%mo;
return rs;
}
void pre(){
int i,j;
jc[0]=jc[1]=ny[0]=ny[1]=1;
fo(i,2,N+M-10)jc[i]=(jc[i-1]*i)%mo;
ny[N+M-10]=ksm(jc[N+M-10],mo-2);
fd(i,N+M-11,2)ny[i]=(ny[i+1]*(i+1))%mo;
}
int C(int n,int m){
return ((jc[n]*ny[m])%mo*ny[n-m])%mo;
}
int gb(int n,int m){
return C(n+m-1,m-1);
}
int main(){
pre();
scanf("%d%d%d",&n,&m,&S);
ans=0;
f[0][0]=1;
fo(i,0,n-1)
fo(j,0,S)
fo(k,0,S-j)
add(f[i+1][j+k],f[i][j]*k%mo);
g[0][0]=1;
fo(i,0,m-1)
fo(j,0,S)
fo(k,0,S-j)
add(g[i+1][j+k],g[i][j]*(k+1)%mo);
fo(x,1,n-1){
t=ksm(S+1,n*m-1-2*(m-1+x-1)-(n-x));
t1=0;
fo(y,0,S){
fo(z,0,y){
temp=gb(x-1,m-1)*f[x-1][z]%mo;
temp=(temp*g[m-1][y-z])%mo;
t1=(t1+temp*gb(S-y,n-x)%mo)%mo;
}
}
add(ans,(t1*t)%mo);
}
fo(x,1,m-1){
t=ksm(S+1,n*m-1-2*(n-1+x-1)-(m-x));
t1=0;
fo(y,0,S){
fo(z,0,y){
temp=gb(x-1,n-1)*g[x-1][z]%mo;
temp=(temp*f[n-1][y-z])%mo;
t1=(t1+temp*gb(S-y,m-x)%mo)%mo;
}
}
add(ans,(t1*t)%mo);
}
printf("%d",ans);
return 0;
}