P1373 小a和uim之大逃离 - DP - 同余

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Fantasy_World/article/details/82390762

得有从题意中抽象出模型的能力
抽象出数学表达,题意求的是使 k 1 k 2 mod ( k + 1 ) 成立的方案数
注意模数是k+1啊,都说了k+1时算作0,k+2时算作1了
其实是数学题。。。
那么 k 1 k 2 mod ( k + 1 ) 其实是比较难求的,除非记录状态,那就要多开一维,思考一下,同余还是可以移项的,现在就是求 k 1 k 2 0 mod ( k + 1 )
因为要交替走,如果说按照一次走两步的走法是不行的,因为你不知道上一个状态是谁走到了,等等。。。我记下来不就好了吗
对于前后两次转移有限制的,需要知道上一次是什么的或是交替走的,一般都是设0/1表示是谁到了这个点,简单来说就是 目前不同的人、不同的情况、不同的决策 导致转移会不一样的时候(比如说换教室)
那么设出状态 f[i][j][k][0/1] 表示是小a/uim到(i,j)时,小a比uim多的魔液(模意义下的)为k的方案数
不能设他们的差是k,因为你不知道谁比谁大,实际上这种状态仍然十分模糊,而这个问题不能忽视这种模糊,就要把状态定的再精确一点,再复杂一点,再“不需要讨论”一点
显然转移是f[i][j][k][0] = f[i-1][j][( k-a[i][j] + (k+1) ) % (k+1)][1] + f[i][j-1][ ( k-a[i][j] + (k+1) ) % (k+1) ][1]
f[i][j][k][1] = f[i-1][j][( k+a[i][j] + (k+1) ) % (k+1)][0] + f[i][j-1][ ( k+a[i][j] + (k+1) ) % (k+1) ][0]
写转移的时候要慢,这个地方容易错
比如第二个方程,因为是减少了a[i][j],所以应该从更大的k转移而来
因为是求同余0,所以负下标的问题就一举解决了
另外注意longlong开不下 可以每次计算时都强转,这样循环结束一层后会释放内存,用时间换空间

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl; 
const int MAXN = 1010;
const int MOD = 1000000000 + 7;
int a[MAXN][MAXN],n,m,kk;
int ans, f[801][801][16][2];
int main() {
    scanf("%d %d %d", &n, &m, &kk);
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            scanf("%d", &a[i][j]);
        }
    }
    int mk = kk+1;
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=m; j++) {
            f[i][j][a[i][j]][0] = 1;
            for(int k=0; k<=kk; k++) {
                f[i][j][k][0] += ( (long long)f[i-1][j][( k-a[i][j] + mk ) % mk][1] + f[i][j-1][( k-a[i][j] + mk ) % mk][1] ) % MOD;
                f[i][j][k][1] += ( (long long)f[i-1][j][( k+a[i][j] + mk ) % mk][0] + f[i][j-1][( k+a[i][j] + mk ) % mk][0] ) % MOD;
            }
            ans = ((long long)ans + f[i][j][0][1]) % MOD;
        }
    }
    printf("%d\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Fantasy_World/article/details/82390762