HDU-OJ-1175 连连看

本文详细解析了连连看游戏中的消去逻辑实现,包括如何通过DFS深度优先搜索算法来判断两个棋子是否可以消去,以及在搜索过程中如何控制转向次数不超过两次。同时,文章还提供了一个具体的C++代码示例,帮助读者更好地理解和实现这一过程。

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

连连看

Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)


Problem Description
“连连看”相信很多人都玩过。没玩过也没关系,下面我给大家介绍一下游戏规则:在一个棋盘中,放了很多的棋子。如果某两个相同的棋子,可以通过一条线连起来(这条线不能经过其它棋子),而且线的转折次数不超过两次,那么这两个棋子就可以在棋盘上消去。不好意思,由于我以前没有玩过连连看,咨询了同学的意见,连线不能从外面绕过去的,但事实上这是错的。现在已经酿成大祸,就只能将错就错了,连线不能从外围绕过。
玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序。
 

Input
输入数据有多组。每组数据的第一行有两个正整数n,m(0<n<=1000,0<m<1000),分别表示棋盘的行数与列数。在接下来的n行中,每行有m个非负整数描述棋盘的方格分布。0表示这个位置没有棋子,正整数表示棋子的类型。接下来的一行是一个正整数q(0<q<50),表示下面有q次询问。在接下来的q行里,每行有四个正整数x1,y1,x2,y2,表示询问第x1行y1列的棋子与第x2行y2列的棋子能不能消去。n=0,m=0时,输入结束。
注意:询问之间无先后关系,都是针对当前状态的!
 

Output
每一组输入数据对应一行输出。如果能消去则输出"YES",不能则输出"NO"。
 

Sample Input
3 4 1 2 3 4 0 0 0 0 4 3 2 1 4 1 1 3 4 1 1 2 4 1 1 3 3 2 1 2 4 3 4 0 1 4 3 0 2 4 1 0 0 0 0 2 1 1 2 4 1 3 2 3 0 0
 

Sample Output
YES NO NO NO NO YES
 

Author
lwg
——————————————————————帅气分割线——————————————————————
思路:这不是我麻麻最喜欢玩的游戏嘛?!没想到写出来它酱不容易。需要在大脑中多开辟一个维度。哦不,是两个维度……其实,都一样嘛!需要有一个想法:增加状态。用状态控制DFS。从起点开始遍历地图,直到找到了终点为止,在此期间,转向次数不得超过2。我们知道DFS是一条路走到黑的,那么一旦此路不通,需要回到上一个还有路没试过的点。什么时候一个点算是彻底不必再访问呢?答案是:1.到该点时已经转向一次,四个方向都不通 2.到该点时已经转向两次,四个方向都不通。vis[x][y][direction][turn]浮现在脑海里。此外,有很多补丁需要打,尤其针对“方向”这个状态量。
这就是附加状态的vis数组。
代码如下:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <memory.h>
#include <stdlib.h>
#define LIM nx >= 1 && nx <= n && ny >=1 && ny <= m
int a[1010][1010];
bool vis[1010][1010][4][2];
int n, m, q, flag;
int sx, sy, ex, ey;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(int x, int y, int dir, int t){
    int d;
    if(t > 2 || flag) return ;//转向次数超过2或者已经断定可以配对,回城
    if(x == ex&&y == ey){//走到了终点
        flag = 1;
        return ;
    }
    vis[x][y][dir][t] = 1;//对该转向次数该访问方向下的该结点做访问标记
    for(d = 0; d < 4; d++){
        int nx = x+dx[d], ny = y+dy[d];
        if(!vis[nx][ny][d][t+(d != dir)] && !a[nx][ny] && LIM)
          dfs(nx, ny, d, t+(d != dir));//t+(d != dir) 是对转向与否的判断。方向与父方向不一致,转向加1
    }
    return ;
}
int main(){
    int i, j, d;
	while(scanf("%d%d", &n, &m) != EOF&&n||m){
        for(i = 1; i <= n; i++)
          for(j = 1; j <= m; j++)
            scanf("%d", a[i]+j);
        scanf("%d", &q);//询问次数
        while(q--){
            flag = 0;
            scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
            if(a[sx][sy] != a[ex][ey])  puts("NO");//起点和终点不是同样的方块,不能配对
            else if(!a[sx][sy] || !a[ex][ey])  puts("NO");//终点或者起点有一个设在了什么都没有的地方,不能配对
            else{//下面在主函数内,对“方向”等等打出补丁
                int u = a[ex][ey];//首先保存终点方块的样子
                a[ex][ey] = 0;//接着将其置为0,不然无法访问终点
                memset(vis, 0, sizeof(vis));
                for(d = 0; d < 4; d++){//从起点开始,有四个方向需要尝试,仅仅走能走通的方向,若不如此做,初始方向无法确定!默认是0(上)的话,vis数组就会出错
                    int x1 = sx+dx[d], y1 = sy+dy[d];
                    if(a[x1][y1] == 0)  dfs(x1, y1, d, 0);
                }
                a[ex][ey] = u;//这个补丁很重要!不恢复终点的样子,下次在“起点或终点有一个为0”的特判会出错!
                if(flag)  puts("YES");
                else  puts("NO");
            }
        }
	}
	return 0;
}



HDU-3480 是一个典型的动态规划问题,其题目标题通常为 *Division*,主要涉及二维费用背包问题或优化后的动态规划策略。题目大意是:给定一个整数数组,将其划分为若干个连续的子集,每个子集最多包含 $ m $ 个元素,并且每个子集的最大值与最小值之差不能超过给定的阈值 $ t $,目标是使所有子集的划分代价总和最小。每个子集的代价是该子集最大值与最小值的差值。 ### 动态规划思路 设 $ dp[i] $ 表示前 $ i $ 个元素的最小代价。状态转移方程如下: $$ dp[i] = \min_{j=0}^{i-1} \left( dp[j] + cost(j+1, i) \right) $$ 其中 $ cost(j+1, i) $ 表示从第 $ j+1 $ 到第 $ i $ 个元素构成一个子集的代价,即 $ \max(a[j+1..i]) - \min(a[j+1..i]) $。 为了高效计算 $ cost(j+1, i) $,可以使用滑动窗口或单调队列等数据结构来维护区间最大值与最小值,从而将时间复杂度优化到可接受的范围。 ### 示例代码 以下是一个简化版本的动态规划实现,使用暴力方式计算区间代价,适用于理解问题结构: ```cpp #include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 10010; int a[MAXN]; int dp[MAXN]; int main() { int T, n, m; cin >> T; for (int Case = 1; Case <= T; ++Case) { cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i]; dp[0] = 0; for (int i = 1; i <= n; ++i) { dp[i] = INF; int mn = a[i], mx = a[i]; for (int j = i; j >= max(1, i - m + 1); --j) { mn = min(mn, a[j]); mx = max(mx, a[j]); if (mx - mn <= T) { dp[i] = min(dp[i], dp[j - 1] + mx - mn); } } } cout << "Case " << Case << ": " << dp[n] << endl; } return 0; } ``` ### 优化策略 - **单调队列**:可以使用两个单调队列分别维护当前窗口的最大值与最小值,从而将区间代价计算的时间复杂度从 $ O(n^2) $ 降低到 $ O(n) $。 - **斜率优化**:若问题满足特定的决策单调性,可以考虑使用斜率优化技巧进一步加速状态转移过程。 ### 时间复杂度分析 原始暴力解法的时间复杂度为 $ O(n^2) $,在 $ n \leq 10^4 $ 的情况下可能勉强通过。通过单调队列优化后,可以稳定运行于 $ O(n) $ 或 $ O(n \log n) $。 ### 应用场景 HDU-3480 的问题模型可以应用于资源调度、任务划分等场景,尤其适用于需要控制子集内部差异的问题,如图像分块压缩、数据分段处理等[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值