HDU 4832 Chess

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=4832

题目大意

n×m 的网格里面, (x0,y0) 位置有个棋子,每步棋子可以向上下左右走 1 或 2 格。现在这个棋子要走k步,问有几种走法?
两种走法不同当仅当存在某一步不同。

数据范围

1n1000
1m1000
1k1000
1x0n
1y0m

思路

基本

1

如果是无限大, result=8k

2

dp[i][j][k] 表示第 k 步走到 (i, j) 的方法数目:

dp[i][j][k]result=t=18dp[it][jt][k1]=i,jdp[i][j][k]

当然时间是不够的。

突破口

要求 k 步走到 (p, q) 的方法数目,可以假设横方向走了 t 步,纵方向走了 k-t 步。这样的条件下,走法数目可以这样表示:

Ctkf(t,p)g(kt,q)

f(i, j) 表示横向走走 i 步走到横坐标是 j 的方法数;同理 g(i, j) 表示纵向。
那么:
result=p,qt=0kCtkf(t,p)g(kt,q)

c,f,g 都可以预处理出来,时间是 O(nm+k(n+m)) ,可是直接计算上面的式子复杂度依然太高。
为此:
result=p,qt=0kCtkf(t,p)g(kt,q)=t=0kCtkp,qf(t,p)g(kt,q)=t=0kCtkp=1nf(t,p)q=1mg(kt,q)

这样就可以 O(k(n+m)) 解决这个问题。

P.S.

其实再处理 f,g 的前缀和的话可以 O(k) 解决第二步。然而预处理时间占主要,只能优化常数级。

评价

不错哟。

代码

#include <bits/stdc++.h>

const int N = 1000 + 5;
const int M = 1000 + 5;
const int K = 1000 + 5;
const int MAXN = N + 5;
const int MAXM = M + 5;
const int MAXK = K + 5;
const int MOD = 9999991;
int c[MAXN][MAXM];
int f[MAXK][MAXN];
int g[MAXK][MAXM];
const int d[] = {1, -1, 2, -2};

void pascal_table() {
  for (int i = 0; i < N; i++)
    c[i][i] = c[i][0] = 1;
  for (int i = 1; i < N; i++) {
    for (int j = 1; j < i; j++)
      c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % MOD;
  }
}

void fg_table(int n, int m, int x, int y) {
  memset(f, 0, sizeof(f));
  memset(g, 0, sizeof(g));
  f[0][x] = 1;
  g[0][y] = 1;
  for (int i = 1; i < K; i++) {
    for (int j = 1; j <= n; j++) {
      f[i][j] = 0;
      for (int k = 0; k < 4; k++) {
        int pre = j + d[k];
        if (pre >= 1 && pre <= n)
          f[i][j] += f[i - 1][pre];
      }
      f[i][j] %= MOD;
    }
    for (int j = 1; j <= m; j++) {
      g[i][j] = 0;
      for (int k = 0; k < 4; k++) {
        int pre = j + d[k];
        if (pre >= 1 && pre <= m)
          g[i][j] += g[i - 1][pre];
      }
      g[i][j] %= MOD;
    }
  }
}

int main() {
  pascal_table();
  int casc;
  scanf("%d", &casc);
  for (int casi = 1; casi <= casc; casi++) {
    int n, m, k;
    int x0, y0;
    scanf("%d %d %d", &n, &m, &k);
    scanf("%d %d", &x0, &y0);
    printf("Case #%d:\n", casi);
    fg_table(n, m, x0, y0);
    int res = 0;
    for (int t = 0; t <= k; t++) {
      int tmp = c[k][t];
      int tmp1 = 0;
      for (int i = 1; i <= n; i++)
        tmp1 = (tmp1 + f[t][i]) % MOD;
      int tmp2 = 0;
      for (int i = 1; i <= m; i++)
        tmp2 = (tmp2 + g[k - t][i]) % MOD;
      tmp = (1LL * tmp * (1LL * tmp1 * tmp2 % MOD)) % MOD;
      res = (res + tmp) % MOD;
    }
    printf("%d\n", res);
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值