当前位置: 首页 > article >正文

(C++回溯算法)微信小程序“开局托儿所”游戏

问题描述

给定一个矩阵 A = ( a i j ) m × n \bm A=(a_{ij})_{m\times n} A=(aij)m×n,其中 a i j ∈ { 1 , 2 , ⋯   , 9 } a_{ij}\in\{1,2,\cdots,9\} aij{1,2,,9},且满足 ∑ i = 1 m ∑ j = 1 n a i j \sum\limits_{i=1}^m\sum\limits_{j=1}^na_{ij} i=1mj=1naij被10整除。玩家每次操作需要选择 A \bm A A中某个所有非空元素之和为10的子矩阵,并将其中所有的元素都标记为空。求按何种顺序消除能将 A \bm A A中所有的元素都标记为空,若存在则返回该解决方案,否则返回空列表。

固定顺序求解

代码

nursery_game.h

#ifndef NURSERY_GAME
#define NURSERY_GAME
#include <vector>
#include <stdint.h>
struct Operate {
	uint8_t x1, y1, x2, y2;
	Operate() {}
	Operate(uint8_t i1, uint8_t j1, uint8_t i2, uint8_t j2):x1(i1), y1(j1), x2(i2), y2(j2) {}
};
// 顺序求解,从左上角开始寻找解决方案
// A: 矩阵A数据,逐行排列
// m: 矩阵A行数
// n: 矩阵A列数
// 注意: m * n < 256
std::vector<Operate> sequentialSolve(int8_t *A, uint8_t m, uint8_t n);
// 逆序求解,从右下角开始寻找解决方案
// A: 矩阵A数据,逐行排列
// m: 矩阵A行数
// n: 矩阵A列数
// 注意: m * n < 256
std::vector<Operate> inverseSolve(int8_t *A, uint8_t m, uint8_t n);
#endif

nursery_game.cpp

#include "nursery_game.h"
#include <utility>
using std::vector;
using std::move;

// #define RECOVER_DATA // 若希望不改变A的值请解开本行注释

#define handle(x1,y1,x2_start,tag) for(uint8_t x2=x2_start;x2<m;){uint8_t ie=x2*n;for(uint8_t y2=y1;y2<n;y2++){uint8_t sum=0;for(uint8_t i=is;i<=ie;i+=n)for(uint8_t j=i+y1,e=i+y2;j<=e;j++){int8_t t=A[j];if(t>0&&(sum+=t)>10)goto tag;}if(sum!=10)continue;vector<uint8_t> set;for(uint8_t i=is;i<=ie;i+=n)for(uint8_t j=i+y1,e=i+y2;j<=e;j++){int8_t t=A[j];if(t>0){A[j]=-t;set.push_back(j);}}R.emplace_back(x1,y1,x2,y2);unRemoveCount-=set.size();posSet.push_back(move(set));goto F_push;}tag:x2++;}

vector<Operate> sequentialSolve(int8_t *A, uint8_t m, uint8_t n) {
	vector<Operate> R;
	vector<vector<uint8_t>> posSet;
	Operate op;
	uint8_t unRemoveCount = m * n, is;
F_push:
	if (!unRemoveCount) {
#ifdef RECOVER_DATA
		int8_t *p = A + m * n;
		do {
			--p;
			*p = -*p;
		} while (p != A);
#endif
		return move(R);
	}
	is = 0;
	for (uint8_t x1 = 0; x1 < m; x1++, is += n)
		for (uint8_t y1 = 0; y1 < n; y1++)
			handle(x1, y1, x1, F1)
F_pop:
	if (R.empty()) return {};
	op = R.back();
	R.pop_back();
	unRemoveCount += posSet.back().size();
	for (auto pos : posSet.back()) A[pos] = -A[pos];
	posSet.pop_back();
	is = op.x1 * n;
	handle(op.x1, op.y1, op.x2 + 1, F2)
	for (uint8_t y1 = op.y1 + 1; y1 < n; y1++) handle(op.x1, y1, op.x1, F3)
	for (uint8_t x1 = op.x1 + 1; x1 < m; x1++) {
		is += n;
		for (uint8_t y1 = 0; y1 < n; y1++) handle(x1, y1, x1, F4)
	}
	goto F_pop;
}

vector<Operate> inverseSolve(int8_t *A, uint8_t m, uint8_t n) {
	vector<Operate> R;
	vector<vector<uint8_t>> posSet;
	Operate op;
	uint8_t unRemoveCount = m * n, is, m1 = m - 1, n1 = n - 1;
F_push:
	if (!unRemoveCount) {
#ifdef RECOVER_DATA
		int8_t *p = A + m * n;
		do {
			p--;
			*p = -*p;
		} while (p != A);
#endif
		return move(R);
	}
	is = m * n;
	for (uint8_t x1 = m1; x1 >= 0; x1--) {
		is -= n;
		for (uint8_t y1 = n1; y1 >= 0; y1--) handle(x1, y1, x1, F1)
	}
F_pop:
	if (R.empty()) return {};
	op = R.back();
	R.pop_back();
	unRemoveCount += posSet.back().size();
	for (auto pos : posSet.back()) A[pos] = -A[pos];
	posSet.pop_back();
	is = op.x1 * n;
	handle(op.x1, op.y1, op.x2 - 1, F2)
	for (uint8_t y1 = op.y1 - 1; y1 >= 0; y1--) handle(op.x1, y1, op.x1, F3)
	for (uint8_t x1 = op.x1 - 1; x1 >= 0; x1--) {
		is -= n;
		for (uint8_t y1 = n1; y1 >= 0; y1--) handle(x1, y1, x1, F4)
	}
	goto F_pop;
}

#undef handle

test.cpp

#include "nursery_game.h"
#include <stdio.h>
using namespace std;

int main() {
	int8_t data[] = { 4,7,3,3,6,5,4,4,2,1,8,4,2,2,2,6,1,2,3,2,3,2,7,1,2,8,1,3,1,6,4,5,4,5,1,4,2,2,2,3,8,3,3,1,9,2,3,3,1,1,4,4,1,9,3,7,1,3,2,5,3,1,1,5 };
	vector<Operate> r(sequentialSolve(data, 8, 8));
	for (auto op : r) printf("(%d,%d) (%d,%d)\n", op.x1, op.y1, op.x2, op.y2);
	return 0;
}

测试结果

(0,1) (0,2)
(0,0) (2,1)
(0,0) (3,1)
(0,7) (1,7)
(1,3) (1,6)
(0,4) (3,4)
(1,3) (5,3)
(0,3) (2,5)
(0,3) (4,5)
(0,4) (6,4)
(2,0) (2,6)
(0,2) (4,2)
(0,2) (4,6)
(5,0) (7,0)
(5,7) (6,7)
(6,0) (7,2)
(5,0) (6,3)
(0,1) (5,6)
(0,5) (7,5)
(4,0) (6,7)
(3,7) (7,7)
(0,0) (7,7)

操作过程:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

按数字大小求解

仔细观察该游戏,我们容易发现往往大数更难以消除,因此我们可以优先消除大数,这样相对来说更容易找到解决方案。

代码

nursery_game.h

#ifndef NURSERY_GAME
#define NURSERY_GAME

// ……
// 固定顺序代码

// 按数字大小的顺序求解
// A: 矩阵A数据,逐行排列
// m: 矩阵A行数
// n: 矩阵A列数
// 注意: m, n < 17
std::vector<Operate> solve(int8_t *A, uint8_t m, uint8_t n);
#endif

nursery_game.cpp

// ……
// 固定顺序代码

#include <unordered_set>
using std::unordered_set;

struct PosNode {
	uint8_t pos, i, j;
	PosNode *next, *pre;
	PosNode():next(nullptr), pre(nullptr) {}
	PosNode(uint8_t p, uint8_t x, uint8_t y, PosNode *parent):pos(p), i(x), j(y), next(nullptr), pre(parent) {}
};

#define Handle(nodeStart,num,tag) for(PosNode*node1=nodeStart;node1;){uint8_t i1,i2,j1,j2;if(i>node1->i){i1=node1->i;i2=i;}else{i1=i;i2=node1->i;}if(j>node1->j){j1=node1->j;j2=j;}else{j1=j;j2=node1->j;}uint16_t key=i1<<12|j1<<8|i2<<4|j2;if(calculated.find(key)==calculated.end()&&pushed->find(key)==pushed->end()){calculated.insert(key);vector<uint8_t>posSet;uint8_t k=0;for(uint8_t i=i1*n,ie=i2*n;i<=ie;i+=n)for(uint8_t j=i+j1,e=i+j2;j<=e;j++){int8_t a=A[j];if(a>0){if((k+=a)>10)goto tag;posSet.push_back(j);}}if(k<10)continue;R.emplace_back(i1,j1,i2,j2);for(auto pos:posSet){A[pos]=-A[pos];node=posToNode[pos];if(!(node->pre->next=node->next))posesEnd[pos]=node->pre;}count-=posSet.size();history.push_back(move(posSet));pushed->insert(key);historyPushed.push_back(pushed);pushed=new unordered_set<uint16_t>;goto F_push;}tag:node1=node1->next;}
#define handle(num,tag) Handle(poses[num]->next,num,tag)
#define handleSame(num,tag) Handle(node->next,num,tag)

vector<Operate> solve(int8_t *A, uint8_t m, uint8_t n) {
	uint8_t N = m * n, count = N;
	PosNode *poses[10], *posesEnd[10], **posToNode = new PosNode * [N];
	for (uint8_t i = 1; i < 10; i++) poses[i] = posesEnd[i] = new PosNode;
	// 在这里遍历的时候还可以判断所有数字的总和是否被10整除,若不然可直接返回。
	// 因为在游戏中不存在不整除的情况,所以没加上这个判断条件。
	for (uint8_t i = 0, p = 0; i < m; i++)
		for (uint8_t j = 0; j < n; j++, p++) {
			PosNode *&node = posesEnd[A[p]];
			posToNode[p] = node = node->next = new PosNode(p, i, j, node);
		}
	vector<Operate> R;
	vector<vector<uint8_t>> history;
	unordered_set<uint16_t> calculated;
	vector<unordered_set<uint16_t> *> historyPushed;
	unordered_set<uint16_t> *pushed = new unordered_set<uint16_t>;
F_push:
	if (!count) {
		PosNode **p = posToNode + N;
		do delete *--p; while (p != posToNode);
		delete[] posToNode;
		p = poses + 9;
		do delete *p; while (--p != poses);
		delete pushed;
		for (auto p : historyPushed) delete p;
#ifdef RECOVER_DATA
		int8_t *q = A + N;
		do {
			q--;
			*q = -*q;
		} while (q != A);
#endif
		return move(R);
	}
	calculated.clear();
	for (PosNode *node = poses[9]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handle(1, F1)
	}
	for (PosNode *node = poses[8]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handle(2, F2)
		handle(1, F3)
	}
	for (PosNode *node = poses[7]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handle(3, F4)
		handle(2, F5)
		handle(1, F6)
	}
	for (PosNode *node = poses[6]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handle(4, F7)
		handle(3, F8)
		handle(2, F9)
		handle(1, F10)
	}
	for (PosNode *node = poses[5]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handleSame(5, F11)
		handle(4, F12)
		handle(3, F13)
		handle(2, F14)
		handle(1, F15)
	}
	for (PosNode *node = poses[4]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handleSame(4, F16)
		handle(3, F17)
		handle(2, F18)
		handle(1, F19)
	}
	for (PosNode *node = poses[3]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handleSame(3, F20)
		handle(2, F21)
		handle(1, F22)
	}
	for (PosNode *node = poses[2]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handleSame(2, F23)
		handle(1, F24)
	}
	for (PosNode *node = poses[1]->next; node; node = node->next) {
		uint8_t i = node->i, j = node->j;
		handleSame(1, F25)
	}
	if (R.empty()) {
		PosNode **p = posToNode + N;
		do delete *--p; while (p != posToNode);
		delete[] posToNode;
		p = poses + 9;
		do delete *p; while (--p != poses);
		delete pushed;
		for (auto p : historyPushed) delete p;
		return {};
	}
	R.pop_back();
	count += history.back().size();
	for (auto pos : history.back()) {
		PosNode *node = posToNode[pos], *&nodeEnd = posesEnd[A[pos] = -A[pos]];
		node->next = nullptr;
		node->pre = nodeEnd;
		nodeEnd = nodeEnd->next = node;
	}
	history.pop_back();
	delete pushed;
	pushed = historyPushed.back();
	historyPushed.pop_back();
	goto F_push;
}

test.cpp

#include "nursery_game.h"
#include <stdio.h>
using namespace std;

int main() {
	int8_t data[] = { 1,2,1,6,5,7,5,1,7,2,6,1,1,8,2,1,9,1,1,4,4,2,4,1,2,3,7,3,3,4,2,1,2,9,1,7,4,4,2,2,4,2,4,8,4,7,1,1,2,1,1,1,1,4,4,1,4,4,1,1,7,1,7,1 };
	vector<Operate> r(solve(data, 8, 8));
	for (auto op : r) printf("(%d,%d) (%d,%d)\n", op.x1, op.y1, op.x2, op.y2);
	return 0;
}

测试结果

(2,0) (2,1)
(4,1) (4,2)
(1,5) (1,6)
(5,3) (7,3)
(3,1) (3,2)
(3,3) (4,3)
(4,3) (4,6)
(6,2) (7,4)
(0,0) (0,3)
(0,4) (2,4)
(2,3) (2,6)
(1,1) (4,3)
(2,5) (4,7)
(0,3) (3,5)
(1,0) (3,7)
(0,6) (6,6)
(4,6) (7,7)
(6,1) (7,6)
(4,1) (6,4)
(5,0) (7,0)
(0,0) (7,7)

操作过程:

0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
G
H
I
J
K
而经过测试,无论是顺序遍历还是逆序遍历在此案例中均耗时较长,但按元素大小遍历并非最优求解算法,如:

A = ( 2 3 2 3 2 1 7 1 6 2 2 4 1 2 3 9 4 2 1 3 3 2 3 2 1 7 7 3 4 2 2 2 2 1 7 2 1 8 1 1 2 5 5 3 4 3 6 3 8 9 1 4 2 4 6 2 2 3 4 1 8 1 1 2 ) \bm A=\begin{pmatrix}2&3&2&3&2&1&7&1\\6&2&2&4&1&2&3&9\\4&2&1&3&3&2&3&2\\1&7&7&3&4&2&2&2\\2&1&7&2&1&8&1&1\\2&5&5&3&4&3&6&3\\8&9&1&4&2&4&6&2\\2&3&4&1&8&1&1&2\end{pmatrix} A= 2641228232271593221775143433234121341428122283417332166119221322

此时,顺序遍历比按大小遍历耗时更短。因此选择求解方法时需要根据具体案例选择不同的方法,具体而言可通过多线程等方式实现最优的求解时间。


http://www.kler.cn/a/387706.html

相关文章:

  • 基于MATLAB+opencv人脸疲劳检测
  • Qt 编写插件plugin,支持接口定义信号
  • Qt初识简单使用Qt
  • 小程序中引入下载到本地的iconfont字体图标加载不出来问题解决
  • vue3+vite搭建脚手架项目本地运行electron桌面应用
  • 大数据技术之Hadoop :我是恁爹
  • 图为科技与广东省北斗移动物联网产业研究院达成战略合作
  • mp3格式音频怎么做成二维码?扫码获取音频文件的制作方法
  • MySQL:客户端工具创建数据库
  • 25浙江省考-28天学行测-Day1
  • Python爬虫基础-正则表达式!
  • Redis4:Redis的Java客户端
  • 计算机网络socket编程(1)_UDP网络编程实现echo server
  • Golang--反射
  • 在Laravel中,最优的自定义验证规则方法
  • k8s和docker的区别及各自的应用场景
  • 快速解锁Rust Slice特性
  • PMP–一、二、三模、冲刺–分类–7.成本管理–技巧–挣值分析
  • 【LuatOS】修改LuatOS源码为PC模拟器添加高精度时间戳库timeplus
  • 十五、Linux线程(二)
  • 使用批处理脚本批量删除Maven无效依赖
  • docker搭建es集群
  • MATLAB-数学建模-无约束规划求解方法(非线性规划)
  • 使用 HuggingFace 提供的 Elasticsearch 托管交叉编码器进行重新排名
  • koa、vue安装与使用
  • ElasticSearch备考 -- Cross cluster replication(CCR)