P1373 小a和uim之大逃离[DP]
题目描述
瞬间,地面上出现了一个n*m的巨幅矩阵,矩阵的每个格子上有一坨0~k不等量的魔液。怪物各给了小a和uim一个魔瓶,说道,你们可以从矩阵的任一个格子开始,每次向右或向下走一步,从任一个格子结束。开始时小a用魔瓶吸收地面上的魔液,下一步由uim吸收,如此交替下去,并且要求最后一步必须由uim吸收。魔瓶只有k的容量,也就是说,如果装了k+1那么魔瓶会被清空成零,如果装了k+2就只剩下1,依次类推。怪物还说道,最后谁的魔瓶装的魔液多,谁就能活下来。小a和uim感情深厚,情同手足,怎能忍心让小伙伴离自己而去呢?沉默片刻,小a灵机一动,如果他俩的魔瓶中魔液一样多,不就都能活下来了吗?小a和他的小伙伴都笑呆了!
现在他想知道他们都能活下来有多少种方法。
解析
很容易看出状态:\(dp[0/1][i][j][l][h]\)分别表示小a和uim走到\((i,j)\)时瓶中的魔液。但是这样写会炸空间。
考虑到最终答案只与两人差值有关,即,从刚开始小a与uim差值为起始点\((i,j)\)的魔液,走到差值为0时为止。
由此,我们定义差值\(k=a-uim\),得到新状态:\(dp[0/1][i][j][k]\)。
考虑小a的转移:上一步由uim吸收,设当前差值为\(k\),则上一步差值为\(a-a[i][j]-uim\),即\(k-a[i][j]\)。
考虑uim的转移:上一步由小a吸收,设当前差值为\(k\),则上一步差值为\(a-(uim-a[i][j])=a-uim+a[i][j]\),即\(k+a[i][j]\)。
由于是方案数,根据加法原理很容易得出状态转移方程。
务必注意,二者的转移是不同的,这与差值的定义有关。
参考代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define N 801
#define mod 1000000007
using namespace std;
int a[N][N],dp[2][N][N][20],n,m,k;//dp[0/1][i][j][k]表示走到(i,j),差值k的方案数,0 a,1 uim
int main()
{
scanf("%d%d%d",&n,&m,&k),++k;//由于k取值0~k,因此对k+1取模
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) scanf("%d",&a[i][j]),dp[0][i][j][a[i][j]%k]=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
for(int t=0;t<k;++t){
dp[0][i][j][t]+=(dp[1][i-1][j][(t-a[i][j]+k)%k]%mod+dp[1][i][j-1][(t-a[i][j]+k)%k]%mod)%mod;//防止溢出
dp[1][i][j][t]+=(dp[0][i-1][j][(t+a[i][j])%k]%mod+dp[0][i][j-1][(t+a[i][j])%k]%mod)%mod;
}
}
int ans=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
ans=(ans%mod+dp[1][i][j][0]%mod)%mod;
printf("%d\n",ans%mod);
return 0;
}