[日常训练] 分割田地

【问题描述】

地主某君有一块由2×n个栅格组成的土地,有k个儿子,现在地主快要终老了,要把这些土地分给这些儿子。
分给每个儿子的土地最小的单位是一个栅格,同时,分给同一个儿子的土地要求要相邻连续的。
地主觉得分给某个儿子的土地面积至少有一个栅格,但是具体多少可以随意。
请问,聪明的你,能够算出地主一共有多少种分土地的方法吗?也就是说要求把2×n的栅格分成K个连通区域,每个区域至少有一个栅格。

【输入格式】

包含两个正整数n和K。

【输出格式】

包含一个整数,为可以分土地的方法数模100000007。

【输入输出样例一】

field.in
2 1

field.out
1

【输入输出样例二】

field.in
5 2

field.out
45

【数据范围】

对于100%的数据:1<=n<=1000,1<=K<=2×n。

【分析】动态规划 + 分类讨论

  • f[i][k][0/1] 表示已经取了第 1 ~i行,分为 k 个连通区域,状态为0 1 时的总方案数
  • 对于状态0/1,我们有如下定义:
    [1]、状态 0 表示第i行的两个栅格分属于两个不同的连通区域
    [2]、状态 1 表示第i行的两个栅格属于同一个连通区域
  • 则状态转移方程为:(这里有一点要注意:这个儿子选这块地,那个儿子选那块地,把两个儿子所选的地交换,仍只算一种方案

  • [1]、 f[i][k][0]+=f[i1][k2][0]
    [2]、 f[i][k][0]+=f[i1][k2][1]
    [3]、 f[i][k][0]+=f[i1][k1][1]×2
    [4]、 f[i][k][0]+=f[i1][k1][0]×2
    [5]、 f[i][k][0]+=f[i1][k][0]

  • [6]、 f[i][k][1]+=f[i1][k1][0]
    [7]、 f[i][k][1]+=f[i1][k1][1]
    [8]、 f[i][k][1]+=f[i1][k][0]×2
    [9]、 f[i][k][1]+=f[i1][k][1]

  • 转移是如何得到的?这是根据第 i 行与第i1行的四个栅格所属连通区域的关系决定,我们通过以下的图来说明(颜色的不同表示所属于不同的连通区域,方块下方的编号表示所对应的转移)
  • 对于 DP 的初值,我们直接考虑第一行的摆放方案,即为 f[1][1][1]=f[1][2][0]=1
  • 则最后答案 Answer=f[n][K][1]+f[n][K][0] ,复杂度为 O(nK)

【代码】

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
const int Mod = 100000007;
const int N = 1005;
int f[N][N << 1][2], n, d;

inline int Min(const int &x, const int &y) {return x < y ? x : y;}

int main()
{
    freopen("field.in", "r", stdin);
    freopen("field.out", "w", stdout);
    scanf("%d%d", &n, &d);
    f[1][2][0] = f[1][1][1] = 1;
    for (int i = 2; i <= n; ++i)
     for (int k = 1; k <= Min((i << 1), d); ++k)
     {
        (f[i][k][0] += (f[i - 1][k - 1][1] << 1)
                     + (f[i - 1][k - 1][0] << 1)
                     + f[i - 1][k - 2][0] 
                     + f[i - 1][k - 2][1]
                     + f[i - 1][k][0]) %= Mod;
        (f[i][k][1] += (f[i - 1][k][0] << 1)
                     + f[i - 1][k - 1][0] 
                     + f[i - 1][k - 1][1]
                     + f[i - 1][k][1]) %= Mod; 
    }
    printf("%d\n", (f[n][d][0] + f[n][d][1]) % Mod);
    fclose(stdin); fclose(stdout);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值