首页 > 其他分享 >UVA1589 象棋 题解

UVA1589 象棋 题解

时间:2023-08-21 12:11:08浏览次数:42  
标签:SEARCH && 题解 象棋 棋子 vskip CHECK UVA1589 define

0. 题目大意

在一个\(10\times9\)的网格上,可以游玩象棋。在本题中,我们考虑如下几个简化的规则:

  • 每一个棋子下在交点上,一个交点不能同时有两个棋子;
  • 棋盘的左上角为\((1,1)\),右下角为\((10, 9)\);
  • 当一个棋子移动到它的敌人的棋子上,就说敌方的棋子要被“吃掉”。

当棋盘上的“将”有被敌人吃掉的风险,我们就说“照将”。当我们的玩家无论如何移动将的位置,都不能避免被敌人吃掉,我们就说“将死”。

简化一下,下面的 4 个棋子的情形如下:

  • 帅,将(General, g):将和帅可以横着或者竖着移动一格,并且正常情况下,无法离开“宫殿”(如原题PDF左上图)。一种特殊的情况是“飞将”。这是指两个将互相照面,中间没有其他的棋子相隔,我们可以让将离开它的宫殿吃掉对面的那个将。
  • 车(Chariot, r):车可以横着或者竖着走任意格,但是不能跳过中间的棋子。
  • 炮(Cannon, c):炮可以像车一样移动,但是能且仅能跳过一个中间相隔的棋子(无论这个棋子是我方的还是敌方的),吃掉下一个。
  • 马(Horse, h):马有8中跳法,如PDF中间图所示。但是,如果有一个棋子在马的周围一格远的话,马就不能往哪个方向跳了。这种情况叫做“别马腿”。

现在给你一个残局,仅仅由黑将,红将,以及若干个红色的车、马、炮组成。现在红方已经照将。写一个程序,看一看这个情况是不是已经将死了。

1. 初步分析

这是一道模拟的问题。简单的想法是:枚举这个将能够走的地方,并且判断是不是能够被吃掉。那么现在的问题转化为,给定一个残局,判定将能不能被吃掉。

要使得将被吃掉,有如下的几种情形:

  • 出现了将帅照面(飞将)的情况;
  • 被车吃掉;
  • 被炮吃掉;
  • 被马吃掉。

对于飞将的情况,主要看这是不是第一次移动。如果是的话,就不会被将死。代码中使用了WIN表示; 反之是LOSE。这有助于保持思路连贯清晰。

#define F(i, a, b) for(int i=a; i<=b; i++)
bool check(char arr[12][12], int x, int y, bool firstmv){
	...
	// Case 1. Is it a flying general? 
	F(i, x+1, 10){
		char &curr = t[i][y];
		if(curr!=0 && curr!='G') break; 
		if(curr == 'G') return firstmv;
	}
    ...
}

由于马和炮都是形成十字形的内容,唯一的不同是中间间隔的棋子个数。所以我们可以同时处理他们。首先假设从当前点向上看有没有有威胁当前将的棋子,可以这样写:

int vskip = 0; // 跳过了几个子?
for(int i=x-1; i>=1; i--){ 
    char &curr = t[i][y]; 
    if(vskip == 0 && curr == 'R') return LOSE; 
    if(vskip == 1 && curr == 'C') return LOSE; 
    if(curr != 0) vskip++; 
    if(vskip >= 2) break; // 跳过2个,结束。
}

但是这样会有点麻烦。因为有4个方向。我们可以巧妙运用#define指令,来让我们的代码更可以阅读。首先对于循环做简化,分别表示正序和倒序循环:

#define F(i, a, b) for(int i=a; i<=b; i++)
#define Fd(i, a, b) for(int i=a; i>=b; i--)

然后设置宏定义:

#define SEARCH(dir, frm, to, current)\
	{int vskip = 0;\
	dir(i, frm, to){\
		char &curr = current;\
		if(vskip == 0 && curr == 'R') return LOSE;\
		if(vskip == 1 && curr == 'C') return LOSE;\
		if(curr != 0) vskip++;\
		if(vskip >= 2) break;\
	}}

之所以用大括号包围起来一层,是避免vskip被重复定义。这样一来,里面所有的参数就可以被替换了——因为C的#define是纯文本替换。我们调试起来就更容易了。具体地,我们只要写如下的代码:

SEARCH(Fd, x-1, 1, t[i][y]);
SEARCH(F, x+1, 10, t[i][y]);
SEARCH(Fd, y-1, 1, t[x][i]);
SEARCH(F, y+1, 9, t[x][i]);

就可以完成这样的功效了。

接下来考虑马可以威胁将的条件,简单画图,就可以写出如下的伪代码:

if(某个方向没有棋子别马腿){
		if(能调过来的地方有马) return LOSE;
}

同样仿照上方的格式,就有

// Case 3. Finally check horse.
	#define CHECK(blkx, blky, realx, realy)\
	if(t[blkx][blky] == 0){\
		if(t[realx][realy] == 'H') return LOSE;\
	}

	// up right
	CHECK(x-1, y+1, x-1, y+2);	CHECK(x-1, y+1, x-2, y+1);	
	// up left
	CHECK(x-1, y-1, x-1, y-2);	CHECK(x-1, y-1, x-2, y-1);	
	// down right
	CHECK(x+1, y+1, x+1, y+2);	CHECK(x+1, y+1, x+2, y+1);	
	// down left
	CHECK(x+1, y-1, x+1, y-2);	CHECK(x+1, y-1, x+2, y-1);	

此外,还需要尝试每一个移动。这就可以移动整个数组。同样可以包装为一个简短的宏定义:

#define TRY_MOVE(tagx, tagy) \
	{char tmpdst = b[tagx][tagy], tmpsrc=b[x][y];b[x][y] = 0;  b[tagx][tagy]='g'; /*存起来*/\
	if(IN_PALACE(tagx, tagy) && check(b, tagx, tagy, 0) == WIN){\
		cout<<"NO"<<endl; \
	return 0;}\
	b[x][y] = tmpsrc, b[tagx][tagy]=tmpdst;} /*放回去*/

到此为止,我们就把大致的框架搭好了。

2. 代码

#include <iostream>
#include <cstring>
using namespace std;
#define N 12
#define F(i, a, b) for(int i=a; i<=b; i++)
#define Fd(i, a, b) for(int i=a; i>=b; i--)
#define IN_BOARD(x, y) (x>=1 && x<=10 && y>=1 && y<=9)
#define IN_PALACE(x, y) (x>=1 && x<=3 && y>=4 && y<=6)
#define WIN 1
#define LOSE 0
#define cerr if(0) cerr
char b[N][N];

bool check(char arr[12][12], int x, int y, bool firstmv){
	// First copy arr to auxiliary arr t.
	char t[N][N]; 
	memcpy(t, b, sizeof(b));
	
	// Case 1. Is it a flying general? 
	F(i, x+1, 10){
		char &curr = t[i][y];
		if(curr!=0 && curr!='G') break; 
		if(curr == 'G') return firstmv;
	}

	// Case 2. Eaten by a chaRiot or Cannon?
	#define SEARCH(dir, frm, to, current)\
	{int vskip = 0;\
	dir(i, frm, to){\
		char &curr = current;\
		if(vskip == 0 && curr == 'R') return LOSE;\
		if(vskip == 1 && curr == 'C') return LOSE;\
		if(curr != 0) vskip++;\
		if(vskip >= 2) break;\
	}}

	SEARCH(Fd, x-1, 1, t[i][y]);
	SEARCH(F, x+1, 10, t[i][y]);
	SEARCH(Fd, y-1, 1, t[x][i]);
	SEARCH(F, y+1, 9, t[x][i]);

	// Case 3. Finally check horse.
	#define CHECK(blkx, blky, realx, realy)\
	if(t[blkx][blky] == 0){\
		if(t[realx][realy] == 'H') return LOSE;\
	}

	// up right
	CHECK(x-1, y+1, x-1, y+2);	CHECK(x-1, y+1, x-2, y+1);	
	// up left
	CHECK(x-1, y-1, x-1, y-2);	CHECK(x-1, y-1, x-2, y-1);	
	// down right
	CHECK(x+1, y+1, x+1, y+2);	CHECK(x+1, y+1, x+2, y+1);	
	// down left
	CHECK(x+1, y-1, x+1, y-2);	CHECK(x+1, y-1, x+2, y-1);	

	cerr<<"FINISH\n";
	return !firstmv;
}

void visualize(bool aux_line){
	if(aux_line){
		for(int i=1; i<=9; i++){
			cerr<<i<<" ";
		}
		cerr<<"\n";
	}
	for(int i=1; i<=10; i++){
		for(int j=1; j<=9; j++){
			if(b[i][j]==0){ cerr<<"+ ";}
			else {cerr<<b[i][j]<<" ";}	
		}
		cerr<<endl;
	}
}

int work(){
	int n, x, y; 
	cin>>n>>x>>y;
	if(!n || !x || !y) return 1;
	memset(b, 0, sizeof b);
	b[x][y]= 'g';
	while(n--){
		char ch; int xi, yi; 
		cin>>ch>>xi>>yi;
		b[xi][yi]=ch;
	}
	visualize(true);
	
	// First check for initial case: 
	if(check(b, x, y, 1) == WIN) {cout<<"NO"<<endl; return 0;}

	// Second, try every movement: 
	#define TRY_MOVE(tagx, tagy) \
	{char tmpdst = b[tagx][tagy], tmpsrc=b[x][y];b[x][y] = 0;  b[tagx][tagy]='g'; \
	if(IN_PALACE(tagx, tagy) && check(b, tagx, tagy, 0) == WIN){\
		cout<<"NO"<<endl; \
		cerr<<"OK at "<<tagx<<" "<<tagy<<endl;\
	return 0;}\
	b[x][y] = tmpsrc, b[tagx][tagy]=tmpdst;}

	TRY_MOVE(x+1, y); TRY_MOVE(x-1, y); TRY_MOVE(x, y+1); TRY_MOVE(x, y-1);
	cout<<"YES"<<endl;
	return 0;
}

int main(){
	int ret=0;
	do{
		ret = work();
	}while(ret != 1);
	return 0;
}

注意:这里的调试信息使用了cerr输出。调试时候被宏定义为了if(0) cerr

标签:SEARCH,&&,题解,象棋,棋子,vskip,CHECK,UVA1589,define
From: https://www.cnblogs.com/augpath/p/17645682.html

相关文章

  • tablestore依赖问题解决
    依赖引入最新版本<dependency><groupId>com.aliyun.openservices</groupId><artifactId>tablestore</artifactId><version>5.16.0</version></dependency>执行如下方法,报错下面2个错误信息,如下图:错误一:错误二:错误原因:JavaSDK依赖2......
  • YACS 2023年8月月赛 乙组 T3 香槟塔 题解
    题目链接乙组中比较好的一道思维题。首先考虑暴力,如果没满就倒满了就往下继续倒,直到倒完或溢出为止,但如果开始就全满然后每次都从最上面倒那么$O(n^2)$就超时了。我们希望找到一个数据结构(当然不是也行)能够快速得到从某个位置向下(包括当前位置)第一个没满的香槟塔,显然并查集。......
  • YACS 2023年8月月赛 乙组 T1 最长回文 题解
    题目链接小清新的区间DP题。看到数据范围以及回文一眼盯真得到是区间DP。设$f[i][j]$为区间$[i,j]$成为回文串最少要经过几次操作,转移一个个看。首先可以删掉第$j$个,$f[i][j]=\min(f[i][j],f[i][j-1]+1)$,同理也可以删掉第$i$个,$f[i][j]=\min(f[i][j],f[i+1][j]+1)$......
  • YACS 2023年6月月赛 乙组 T3 工作安排 题解
    这道题是乙组里比较新奇的一题,本来一眼看下来不会,后来蒙了个按照单位时间内收到罚款排序居然对了,十分意外。简单的证明一下:假设有两个工作,时间分别为$t_1$$f_1$$t_2$$f_2$,假设把第一个放在前面更优,前面的罚款不变。则有$t_1\timesf_1+(t_1+t_2)\timesf_2<t_2\timesf_2+(......
  • [2023 上半年] [软件设计师] [下午题] 题解报告
    2023年下午题整体难度有所上升,取消了简单和困难难度,全部设置为中等难度。第一题数据流图随着农业领域科学种植的发展,需要对农业基地及农事进行信息化管理,为租户和农户等人员提供种植相关服务。现欲开发农事管理服务平台,其主要功能是:(1)人员管理。平台管理员管理租户;租户管理农户并......
  • [ABC297G] Constrained Nim 2 题解
    题意有\(N\)堆石子,其中第\(i\)堆有\(A_i\)个石子。每次可以选一堆从中取\(\left[L,R\right]\)个,问判断先手后手胜负。(\(1\leN\le2\times10^5,1\leL\leR\le10^9,1\leA_i\le10^9\))。题解考虑子游戏,即只有一堆石子的情况,考虑其\(\operatorname{SG}\)......
  • CF1823F Random Walk 题解
    题意给定一棵由\(n\)个节点组成的树,定义每次移动的方式为等概率的移动到相邻节点上,询问从\(s\)移动到\(t\)的过程中每个点的期望经过次数。(\(1\len\le2\times10^5\))。题解定义\(f_i\)为节点\(i\)的期望经过次数,\(fa_u\)为节点\(u\)的父亲节点,\(\operatorna......
  • 「TAOI-2」Break Through the Barrier 题解
    前言:比赛前去做牙齿矫正,回来晚了10分钟……做比赛的运气全用在了一路绿灯上了(无语)。第二题切了两个半小时。决定写篇题解来抒发一下再记得愤怒愉悦之情。AC的想法很简单,就是表示出每一串连续的\(\texttt{T}\),其长度分别为\(l_1\liml_m\)。明显的,对于任何一个\(l_i\),在一......
  • P1345 [USACO5.4] 奶牛的电信Telecowmunication 题解
    P1345[USACO5.4]奶牛的电信Telecowmunication题目描述农夫约翰的奶牛们喜欢通过电邮保持联系,于是她们建立了一个奶牛电脑网络,以便互相交流。这些机器用如下的方式发送电邮:如果存在一个由\(c\)台电脑组成的序列\(a_1,a_2,\cdots,a_c\),且\(a_1\)与\(a_2\)相连,\(a_2\)与......
  • P9556 [SDCPC2023] A-Orders 题解
    题目传送门一道模拟题。可以命名一个订单的结构体,然后将订单的结束时间进行排序。用一个变量模拟货物的数量,每遇到一个订单,货物的数量就会加上距离上一个订单的天数乘上\(k\)。即对于第\(i\)个订单,距离第\(i-1\)订单货物数量增加了\((a_{i}-a_{i-1})\timesk\)。如果在模......