洛谷P2327 [SCOI2005]扫雷【DP】【黄】

本文介绍了一种特殊的万圣节扫雷游戏规则,玩家需要根据第二列的雷数限制推断第一列的不同摆放方案。通过动态规划求解状态转移方程,提供了代码实现和解决方案。

Date:2022.02.09
题目描述
相信大家都玩过扫雷的游戏。那是在一个n×m的矩阵里面有一些雷,要你根据一些信息找出雷来。万圣节到了,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则和扫雷一样,如果某个格子没有雷,那么它里面的数字表示和它8连通的格子里面雷的数目。现在棋盘是n×2的,第一列里面某些格子是雷,而第二列没有雷,如下图:
在这里插入图片描述
由于第一列的雷可能有多种方案满足第二列的数的限制,你的任务即根据第二列的信息确定第一列雷有多少种摆放方案。
输入格式
第一行为N,第二行有N个数,依次为第二列的格子中的数。(1<= N <= 10000)
输出格式
一个数,即第一列中雷的摆放方案数。
输入输出样例
输入 #1复制
2
1 1
输出 #1复制
2

思路:定义状态f[i][j][k]f[i][j][k]f[i][j][k],表示------第iii个格子的状态为jjj,第i+1i+1i+1个格子的状态为kkk的方案数。其中状态为0表示无雷,状态为1表示有雷。开始fff写了个四维的,三维也一样。其中,初始化时f[0][0][0]很显然是1f[0][0][0]很显然是1f[0][0][0]1,而f[0][0][1]f[0][0][1]f[0][0][1]中核心点为i==0i==0i==0,但由于在矩阵外边影响不到矩阵里边,因此也算是一种可行方案,初始化也为1。
状态转移方程就要分为四类:
a[i]==0:f[i][0][0]+=f[i−1][0][0];a[i]==0:f[i][0][0]+=f[i-1][0][0];a[i]==0:f[i][0][0]+=f[i1][0][0];
a[i]==1:f[i][0][1]+=f[i−1][0][0];f[i][0][0]+=f[i−1][1][0];f[i][1][0]+=f[i−1][0][1];a[i]==1:f[i][0][1]+=f[i-1][0][0]; f[i][0][0]+=f[i-1][1][0]; f[i][1][0]+=f[i-1][0][1];a[i]==1:f[i][0][1]+=f[i1][0][0];f[i][0][0]+=f[i1][1][0];f[i][1][0]+=f[i1][0][1];
a[i]==2:f[i][1][0]+=f[i−1][1][1];f[i][0][1]+=f[i−1][1][0];f[i][1][1]+=f[i−1][0][1]a[i]==2:f[i][1][0]+=f[i-1][1][1]; f[i][0][1]+=f[i-1][1][0]; f[i][1][1]+=f[i-1][0][1]a[i]==2:f[i][1][0]+=f[i1][1][1];f[i][0][1]+=f[i1][1][0];f[i][1][1]+=f[i1][0][1]
a[i]==3:f[i][1][1]+=f[i−1][1][1];a[i]==3:f[i][1][1]+=f[i-1][1][1];a[i]==3:f[i][1][1]+=f[i1][1][1];
最终求解答案ansansans时,也是讨论最后一个格子:
a[i]==0:ans=f[n][0][0];a[i]==0: ans=f[n][0][0];a[i]==0:ans=f[n][0][0];
a[i]==1:ans=f[n][1][0]+f[n][0][0];a[i]==1: ans=f[n][1][0]+f[n][0][0];a[i]==1:ans=f[n][1][0]+f[n][0][0];
a[i]==2:ans=f[n][1][0];a[i]==2: ans=f[n][1][0];a[i]==2:ans=f[n][1][0];
a[i]==3:不可能。a[i]==3: 不可能。a[i]==3:
代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
typedef long long LL;
LL n,m,a[N],f[N][3][3];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    f[0][0][0]=f[0][0][1]=1;
    for(int i=1;i<=n;i++)
    {
        if(a[i]==0) f[i][0][0]+=f[i-1][0][0];
        else if(a[i]==1)
        {
            f[i][0][1]+=f[i-1][0][0];
            f[i][0][0]+=f[i-1][1][0];
            f[i][1][0]+=f[i-1][0][1];
        }
        else if(a[i]==2)
        {
            f[i][1][0]+=f[i-1][1][1];
            f[i][0][1]+=f[i-1][1][0];
            f[i][1][1]+=f[i-1][0][1];
        }
        else f[i][1][1]+=f[i-1][1][1];
    }
    LL ans=0;
    if(a[n]==0) ans=f[n][0][0];
    else if(a[n]==1) ans=f[n][1][0]+f[n][0][0];
    else if(a[n]==2) ans=f[n][1][0];
    //不可能三个都是1,因为角上的八个格子内最多两个雷
    cout<<ans;
    return 0;
}
### 解题思路 P4160 [SCOI2009] 生日快乐 这道题的核心在于递归与分治策略。题目要求将一个矩形蛋糕切成若干块,使得每一块的长宽比的最大值最小。由于每一块的长宽比是独立的,因此可以通过递归的方法,将问题分解为子问题来求解。 #### 核心思路: 1. **递归切分**:每次将蛋糕分成两部分,并递归地对这两部分进行同样的操作,直到只剩一块为止。 2. **枚举切分方式**:对于每一层递归,需要枚举所有可能的切分方式(横向或纵向),以及每一块的大小比例。 3. **取最大值与最小值**:每一步递归中,选择切分方式使得两部分的最大长宽比尽可能小。 #### 关键点: - **长宽比处理**:为了保证长宽比的计算准确,需要确保长边在分子,短边在分母。 - **切分方式枚举**:枚举所有可能的切分比例,确保没有遗漏。 - **递归终止条件**:当只剩一块时,直接返回当前长宽比。 ### 代码示例 以下是一个完整的代码实现,展示了如何通过递归方法解决这个问题: ```cpp #include <iostream> #include <cstdio> #include <algorithm> using namespace std; int x, y, n; // 计算最大公约数 int gcd(int x, int y) { if (y == 0) return x; return gcd(y, x % y); } // 递归函数,用于计算最小的长宽比 double qie(int x, int y, int n) { if (x < y) swap(x, y); // 保证x是较长边 int g = gcd(x, y); if (g != 1) { x /= g; y /= g; } if (n == 1) return static_cast<double>(x) / y; // 终止条件 double ans = 10000000; for (int i = 1; i < n; ++i) { // 横向切分 ans = min(ans, max(qie(x * i, y * n, i), qie(x * (n - i), y * n, n - i))); // 纵向切分 ans = min(ans, max(qie(x * n, y * i, i), qie(x * n, y * (n - i), n - i))); } return ans; } int main() { scanf("%d%d%d", &x, &y, &n); printf("%.6lf\n", qie(x, y, n)); return 0; } ``` ### 代码解析 1. **gcd函数**:用于化简长宽比,避免浮点数计算误差。 2. **qie函数**: - 首先交换长宽,确保长边在前。 - 化简长宽比的分数,避免重复计算。 - 递归终止条件:当只剩一块时,返回长宽比。 - 枚举所有可能的切分方式,取最小的长宽比。 3. **main函数**:读取输入并调用递归函数,输出结果。 ### 复杂度分析 - **时间复杂度**:由于每次递归会枚举所有可能的切分方式,时间复杂度为指数级,但由于数据范围较小,可以通过递归直接解决。 - **空间复杂度**:递归深度由切分次数决定,空间复杂度较低。 ### 总结 这道题通过递归的方式,将大问题分解为子问题,结合枚举所有可能的切分方式,最终找到最优解。递归与分治策略是解决此类问题的核心思想。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值