AcWing 674.超级2048

题目链接: 674. 超级2048 - AcWing题库

题目描述:

2048 是一个著名的单人游戏,其目标是在网格上滑动图块以组合它们并创建数字为2048的图块。

2048 在一个简单的4×4 网格上进行游戏,其中有一些图块,玩家可以对它们进行移动。

每一步操作,玩家可以选择向左,向右,向上和向下 4 个方向去移动所有图块。

如果两个包含数字相同的图块在移动时发生碰撞,则它们将合并为一个图块,该图块上的数值为两个图块的数字之和。

在一次操作中,一个新创建的图块不能再次参与合并,并且图块们总是优先沿着移动方向与其旁边的图块进行合并。

例如,如果三个 2 在一行构成 2 2 2,这时玩家选择向左移动,则它将变为 4 2 0,最左边的两个 22 会被合并。

untitled.png

上图显示了当玩家将所有图块“右移”时 4×4 网格的变化情况。

爱丽丝和鲍勃非常喜爱这个游戏。

但是几轮后,他们觉得网格的尺寸太小了并不刺激,所以决定将网格的尺寸扩大到 N×NN×N,他们称之为“超级 2048”游戏。

但是难度的激增让他们无法驾驭这个游戏。

他们要求你编写一个程序来帮助他们弄清楚在所有图块沿着某个特定方向移动后网格的样子。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。

每组数据第一行包含整数 N 和一个字符串 DIR,其中 NN 表示网格的尺寸,DIR 表示图块的移动方向,DIR 将会是以下四个字符串中的一个:leftrightupdown

接下来 N 行,每行包含 N 个整数,用来描述网格的初始状态,每个整数表示网格中一个图块的值,如果为 0 则表示该位置是空的。

输出格式

对于每组测试数据,第一行输出 Case #x:,其中 x 为组别编号(从 1 开始)。

然后输出 N 行,每行 N 个空格隔开的整数,表示图块移动后网格的样子。

数据范围

1≤T≤100
1≤N≤20
网格中的每个数字都是 0 或 2 到 1024 之间的 2 的整数次幂,包括 2 和 1024。

输入样例:

3
4 right
2 0 2 4
2 0 4 2
2 2 4 8
2 2 4 4
10 up
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
3 right
2 2 2
4 4 4
8 8 8

输出样例:

Case #1:
0 0 4 4
0 2 4 2
0 4 4 8
0 0 4 8
Case #2:
4 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
Case #3:
0 2 4
0 4 8
0 8 16

思想

(此距离为右向移动,图片丑请勿怪):

 定义start下标指针,指向遍历的起点,idx初始值为遍历的开始起点也就是start的初始值

由start开始遍历,由新的 k 下标指针(start - 1),进行遍历到start当前位置之后(此“之后为遍历的下一个位置的意思),当遇到不为0的时候停止。查看当前 k 和 start指向值的关系,如果是相同的那么也就发生了“碰撞”行为(2048游戏中移动时相邻的相同值会被合并,以下我简称”碰撞“),那么将 idx 指向的下标位置的值赋值为 当前 start 值指向的两倍、如果没有发生”碰撞“,那么将 idx 指向的下标位置的值赋值为 当前 start 值,随后,将 start 的下标位置挪动到当前 k 下标所指向的位置。此图为第一次遍历之后所产生的情况。

当最后一次遍历之后会是下图的情况

 完成之后,在以idx为遍历起点,将之后的值初始化为‘空’(0值),即可

先是最为初始化的思想,将上下左右的移动,写出为四个函数:

之后将是把四个情况融合在一起:

#include <bits/stdc++.h>
using namespace std;

const int N = 30;
int arr[N][N];

void getUp(int n);
void getDown(int n);
void getLeft(int n);
void getRight(int n);

int main() {
    int t;cin >> t;
    for (int t1 = 1;t1 <= t;t1 ++) {
        int n;
        string str;
        cin >> n >> str;
        
        for (int i = 0;i < n;i ++) 
            for (int j = 0;j < n;j ++) cin >> arr[i][j];
        
        if (str == "up") getUp(n);
        
        if (str == "down") getDown(n);
        if (str == "left") getLeft(n);
        if (str == "right") getRight(n);
        
        cout << "Case #" << t1 << ":" << endl;
        for (int i = 0;i < n;i ++) {
            for (int j = 0;j < n;j ++) cout << arr[i][j] << ' ';
            cout << endl;
        }
    }
    return 0;
}

void getUp(int n){
    for (int i = 0;i < n;i ++) { // 遍历每一列
        int idx = 0;
        
        for (int j = 0;j < n;j ++) {
            if (arr[j][i] == 0) continue; // j => 行号
           
            
            int k = j + 1;
            
            while (k < n && arr[k][i] == 0) k ++;
            
            if (k < n && arr[j][i] == arr[k][i]) {
                arr[idx ++][i] = 2 * arr[j][i];
                j = k;
            }
            else arr[idx ++][i] = arr[j][i];
        }
        
        for (int j = idx;j < n;j ++) arr[j][i] = 0;
    }
}

void getDown(int n){
    for (int i = 0;i < n;i ++) { // 遍历每一列
        int idx = n - 1;
        
        for (int j = n - 1;j >= 0;j --) {
            if (arr[j][i] == 0) continue; // j => 行号
            
            int k = j - 1;
            
            while (k >= 0 && arr[k][i] == 0) k --;
            
            if (k >= 0 && arr[j][i] == arr[k][i]) {
                arr[idx --][i] = 2 * arr[j][i];
                j = k;
            }
            else arr[idx --][i] = arr[j][i];
        }
        
        for (int j = idx;j >= 0;j --) arr[j][i] = 0;
    }
}

void getLeft(int n){
    for (int i = 0;i < n;i ++) {
        int idx = 0;
        for (int j = 0;j < n;j ++) {
            if (arr[i][j] == 0) continue;
            
            
            int k = j + 1;
            
            while (k < n && arr[i][k] == 0) k ++;
            
            if (k < n && arr[i][j] == arr[i][k]) {
                arr[i][idx ++] = 2 * arr[i][k];
                j = k;
            }
            else arr[i][idx ++] = arr[i][j];
        }
        
        for (int j = idx;j < n;j ++) arr[i][j] = 0;
    }
}

void getRight(int n){
    for (int i = 0;i < n;i ++) {
        int idx = n - 1;
        for (int j = idx;j >= 0;j --) {
            if (arr[i][j] == 0) continue;
            
            
            int k = j - 1;
            
            while (k >= 0 && arr[i][k] == 0) k --;
            
            if (k >= 0 && arr[i][j] == arr[i][k]) {
                arr[i][idx --] = 2 * arr[i][k];
                j = k;
            }
            else arr[i][idx --] = arr[i][j];
        }
        
        for (int j = idx;j >= 0;j --) arr[i][j] = 0;
    }
}

将四种情况写到了一种函数当中:

#include <bits/stdc++.h>
using namespace std;

const int N = 30;
int arr[N][N];

void check(int n, int fleft, int fright);

int main() {
    int t;cin >> t;
    for (int t1 = 1;t1 <= t;t1 ++) {
        int n;string str;
        cin >> n >> str;
        
        for (int i = 0;i < n;i ++) 
            for (int j = 0;j < n;j ++) cin >> arr[i][j];
        
        if (str == "up") check(n, 0, 1);
        if (str == "down") check(n, 0, -1);
        if (str == "left") check(n, 1, 0);
        if (str == "right") check(n, -1, 0);
        
        cout << "Case #" << t1 << ":" << endl;
        for (int i = 0;i < n;i ++) {
            for (int j = 0;j < n;j ++) cout << arr[i][j] << ' ';
            cout << endl;
        }
    }
    return 0;
}

/* 解决2048游戏中横竖向移动问题, 其中arr是存储为int类型的2048图
   @parma n 当前移动位置的长度, 默认区间在[0, n-1]中
   @parma frow 当前是否横向移动, frow > 0 => 向右移动, frow < 0 => 向左移动, frow == 0 不移动
   @parma fcol 当前是否纵向移动, fcol > 0 => 向下移动, fcol < 0 => 向上移动, fcol == 0 不移动
*/ 
void check(int n, int frow, int fcol) {
    // 判断当前是否出现了不移动,或者横纵向同时移动的状态,此为错误的操作
    if ((frow == 0 && fcol == 0) || (frow != 0 && fcol != 0)) return;
    
    int start, flag; // 定义开始遍历的起点下标, 遍历的方向
    // 当flag = 1时候,即为从左往右(从上往下)开始遍历, 为-1的时候就相反
    // ----------------相应的start起点下标也就是0,----------------n - 1
    if (frow > 0 || fcol > 0) start = 0, flag = 1;
    else start = n - 1, flag = -1;
    
    for (int i = 0;i < n;i ++) { // 开始横线或纵向遍历
        //定义一个和start同向且初始值相同的下标指针,当2048开始移动时发生碰撞产生改变时
        //存储点的下标位置
        int idx = start; 
        
        //开始纵向或横线遍历,这取决外层循环的涵义
        for (int j = start;j >= 0 && j < n;j += flag) {
            //当frow != 0,表示现在外层循环是一个纵向遍历,内层循环是一个横向遍历…………
            //即在遍历过程中遇到0,也就是2048游戏中的空位的时候,进行一个跳过
            if ((frow != 0 && arr[i][j] == 0) || (fcol != 0 && arr[j][i] == 0)) continue;
            
            //定义k指针为当前j指针的后一个(此前并非为j + 1,而是遍历元素的后一个,与遍历方向有关
            int k = j + flag;
            
            //用于跳过j指针后存在的'空'(0值),直到找到相同值或不同值
            if (fcol != 0) while (k >= 0 && k < n && arr[k][i] == 0) k += flag;//纵向遍历
            else while (k >= 0 && k < n && arr[i][k] == 0) k += flag;//横向遍历
            
            //找到需要处理的值之后,判断横纵向遍历
            if (frow == 0) {
                //如果当前 k 指针没有越界行为,并且与 j 指针指向的值相同,即发生碰撞
                if (k >= 0 && k < n && arr[j][i] == arr[k][i]) 
                    arr[idx][i] = 2 * arr[j][i], j = k;
                else arr[idx][i] = arr[j][i];// 有不同值则赋值到存储点
            }
            else {
                if (k >= 0 && k < n && arr[i][j] == arr[i][k]) 
                    arr[i][idx] = 2 * arr[i][j], j = k;
                else arr[i][idx] = arr[i][j];
            }
            
            //处理完一次碰撞或没有碰撞之后,存储点的位置也要跟随start遍历的方向挪动
            idx += flag;
        }
        //移动处理完之后,剩余的部分理应为'空'(0值)
        for (int j = idx;j >= 0 && j < n;j += flag)
            //判断横纵向遍历,进行赋值
            frow == 0 ? arr[j][i] = 0 : arr[i][j] = 0;
    }
}

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目链接:https://www.acwing.com/problem/content/4948/ 题目描述: 给定一棵有 $n$ 个结点的树,结点从 $1$ 到 $n$ 编号,每个结点都有一个权值 $w_i$,现在有 $m$ 次操作,每次操作是将树中编号为 $x$ 的结点的权值加上 $y$,然后询问一些节点是否为叶子节点,如果是输出 $1$,否则输出 $0$。 输入格式: 第一行包含两个整数 $n$ 和 $m$。 第二行包含 $n$ 个整数,其中第 $i$ 个整数表示结点 $i$ 的初始权值 $w_i$。 接下来 $n-1$ 行,每行包含两个整数 $a$ 和 $b$,表示点 $a$ 和点 $b$ 之间有一条无向边。 接下来 $m$ 行,每行描述一次操作,格式为三个整数 $t,x,y$。其中 $t$ 表示操作类型,$t=1$ 时表示将编号为 $x$ 的结点的权值加上 $y$,$t=2$ 时表示询问编号为 $x$ 的结点是否为叶子节点。 输出格式: 对于每个操作 $t=2$,输出一个结果,表示询问的结点是否为叶子节点。 数据范围: $1≤n,m≤10^5$, $1≤w_i,y≤10^9$ 样例: 输入: 5 5 1 2 3 4 5 1 2 1 3 3 4 3 5 2 3 0 1 3 100 2 3 0 1 1 100 2 3 0 输出: 1 0 0 算法1: 暴力dfs,每次都重新遍历整棵树,时间复杂度 $O(nm)$ 时间复杂度: 最坏情况下,每次操作都要遍历整棵树,时间复杂度 $O(nm)$,无法通过此题。 算法2: 用一个 vector<int> sons[n+5] 来存储每个点的所有子节点,这样可以用 $O(n)$ 预处理出每个点的度数 $deg_i$,如果 $deg_i=0$,则 $i$ 是叶子节点,否则不是。 对于每个操作,只需要更新叶子节点关系的变化就可以了。如果某个节点的度数从 $1$ 变成 $0$,则该节点变成了叶子节点;如果某个节点的度数从 $0$ 变成 $1$,则该节点不再是叶子节点。 时间复杂度: 每次操作的时间复杂度是 $O(1)$,总时间复杂度 $O(m)$,可以通过此题。 C++ 代码: (算法2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值