简单递推 - 动态规划 - LightOJ - 1005 - Rooks

本文探讨了一个关于在n×n大小的棋盘上放置k颗棋子的问题,提出了一种动态规划解法,并给出了相应的状态转移方程及C++实现代码。此外,还提供了一种简单的组合求和方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:[点这儿].

题意:

这里写图片描述
一个棋盘,每个格子可以放一个棋子,放了棋子的这一行和这一列就不能再放其他棋子了,问你对于规模为n的棋盘,上面放k颗棋子有多少种放法.其中 1<=n<=30,0<=k<=n2 .

解析:

首先,这个题组合放法可以做,而且比较简单,待会会给出,现在我们说说动态规划的做法.

这是一个简单递推动态规划,定义dp[i][j][k]为规模为iXj的棋盘上面放k颗棋子的方法数,那么根据下面这些图就很容易得出状态转移方程.
这里写图片描述

说明一下这个图,红色格子会放一个棋子,白色和绿色格子不会放棋子,橙色格子区是放好某些数量的棋子的区域,而灰色格子是可以放棋子但是没放的区域,

  • a :格子(i,j)放一个棋子,故dp[i]j][k] += dp[i - 1][j - 1][k - 1]
  • b : 白色的格子不放棋子,那么棋子都集中在橙色区域,故dp[i][j][k] += dp[i - 1][j - 1][k]
  • c : 灰色区域加红色格子可以放一个棋子,不过这样会导致出现绿色区域,故dp[i][j][k] += (j - 1) * dp[i - 1][j - 2]
  • d : 情况同c ,故dp[i][j][k] += (i - 1) * dp[i - 2][j - 1]
  • e : 横着的灰色区域加红色格子和竖着的灰色区域加红色格子,各可以放一个棋子,这样就会导致出现e 中的绿色区域,故dp[i][j][k] += (i - 1) * (j - 1) * dp[i - 2][j - 2][k - 2]

综上,得出状态转移方程:

dpi,j,k=dpi1,j1,k1+dpi1,j1,k+(j1)dpi1,j2,k1+(i1)dpi2,j1,k1+(j1)(i1)dpi2,j2,k2

代码:

#include <bits/stdc++.h>

using namespace std;

#define N 30

typedef long long LL;

LL dp[N + 1][N + 1][N + 1];

void init()
{
    for (int i = 0; i <= N; i++)
        for (int j = 0; j <= N; j++)
            dp[i][j][0] = 1;
    for (LL k = 1; k <= N; k++)
        for (LL i = 1; i <= N; i++)
            for (LL j = 1; j <= N && k <= max(i, j); j++)
                dp[i][j][k] = dp[i - 1][j - 1][k - 1] + dp[i - 1][j - 1][k] +
                    (j - 1) * dp[i - 1][j - 2][k - 1] + (i - 1) * dp[i - 2][j - 1][k - 1] +
                    (j - 1) * (i - 1) * dp[i - 2][j - 2][k - 2];
}

int main()
{
    init();
    int T, K = 1;
    for (scanf("%d", &T); T--; K++) {
        int n, k;
        scanf("%d%d", &n, &k);
        printf("Case %d: %lld\n", K, k <= n ? dp[n][n][k] : 0);
    }
    return 0;
}

其他方法:

组合求和这道题就太简单了,这么想,从n行中选k行,从n列选k列出来,这样,选出来的行列唯一构成一个二元组(i,j),然后再把这k个二元组排列一下就可以了.

公式:

0(kn)(kn)n!k>nk<=n

代码就不写了.

注意:

这个题范围给的不是很好,首先我们来看下自己给出的输入输出数据.

输入

30
1 1
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
10 9
11 10
12 11
13 12
14 13
15 14
16 15
17 16
18 17
19 18
20 19
21 20
22 21
23 22
24 23
25 24
26 25
27 26
28 27
29 28
30 29

AC的输出

Case 1: 1
Case 2: 4
Case 3: 18
Case 4: 96
Case 5: 600
Case 6: 4320
Case 7: 35280
Case 8: 322560
Case 9: 3265920
Case 10: 36288000
Case 11: 439084800
Case 12: 5748019200
Case 13: 80951270400
Case 14: 1220496076800
Case 15: 19615115520000
Case 16: 334764638208000
Case 17: 6046686277632000
Case 18: 115242726703104000
Case 19: 2311256907767808000
Case 20: -6682192057595854848
Case 21: 2998629330744246272
Case 22: -9067791737139757056
Case 23: 2483266474485481472
Case 24: -3577022814806343680
Case 25: -8604058797746421760
Case 26: -3914123377064804352
Case 27: -484513635729670144
Case 28: -1087798259688144896
Case 29: -1708620176192176128
Case 30: -4682952033483882496

从样例可以看出,当k=n-1时数字最大,因此,我给出了上述输入样例,可以发现,当n>19后就溢出了,我估计后台数据也是这样的,题目中为了避免大整数,特意告诉你“You may safely assume that this number will be less than 1017 .”,你就按这么多来,long long就够了,我一开始没看到这句话,测试了n=30,k=29后我都不敢交!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值