中国象棋将帅问题

 
 
将帅问题
俗话说的好,将帅不见面,见面分胜负。残局之中只有将帅,如何保持僵局? 假设A表示将,B表示帅。如何输出他们的合法位置?请写一个程序来输出。



	当你看到这个题目的时候,可能感觉很简单,无非是两个个for循环而已,只要A、B不在同一列即可。可是你如何表示表示棋盘,如何表示棋局呢?很麻烦?是吧
	哈哈,其实这根本不是重点,棋局什么的都不重要,重要的是A、B的合法位置。这个时候就要学会转化一下,数学建模,把这棋局转化为逻辑坐标。也就是说,我们只要一个3×3的数组就可以表示他们的所有位置了。要看清本质问题啊,不要想那些没用的。

类似的 A=4,B=8就是一个合法位置,ok,let’s programming

int main()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(i%3!=j%3)
				cout<<"A="<<i<<"  B="<<j<<endl;
		}
	}
	return 0;
}


	呵呵,还不错。可是如果我说只能一个变量呢?你能想到什么?一个变量,用变量的一半当作i,另一半用来当作j。这时候你可能会想到位结构体,这个位结构体不赞成使用,因为它降低程序的可移植性。不过,有时候会起到很巧妙的效果,就像这个题目。
struct A
{
	unsigned int a:4;
	unsigned int b:4;
};
int main()
{
	A i;
	for(i.a=1;i.a<=9;i.a++)
	{
 		for(i.b=1;i.b<=9;i.b++)
		{
	 		if(i.a%3!=i.b%3)
 				cout<<"A="<<i.a<<"  B="<<i.b<<endl;
		}
	}
	return 0;
}

	几乎和刚才的代码没什么区别。不错。
	还有别的好的办法吗?你会用到位操作吗?一个char类型8位 ,4位足够表示9个数,所以用一个char的左边4位和右边4位来代替i,j是完全可以的。可是很麻烦的,要进行或、与操作,分别取一个char型的左边4位和右边4位。可是我们要掌握这种方法,位操作通常也会解决一些很麻烦的问题,常常有巧妙的解法。
	如果我们利用一个8位的char变量,需要提取他的左边4位和右边四4位的值。这只需要简单的相与相或加移位就可以办到了。
获取a的前四位,只需要((a&240)>>4)  a=10100101
举例  
     	  10100101 (a)
	& 11110000 (240)
	-----------------
 	  10100000>>4=00001010

获取a的前四位,只需要(a&15) a=10100101

举例      10100101 (a)       & 00001111 (15) -------------- 00000101 可是我们如何给循环变量加增量呢?同样利用位操作。 给右边四位赋值(重置),同时还要保持左边四位的值。因为这要代替两个变量,不能相互影响,和获取不同的是改变了的值要保存。 a的右边四位重置为n,同时保持左边四位不变。(a=(240&a)|n) 举例 a=10100101  n=1     11110000 (240) & 10100101 (a) -----------------    10100000 | 00000001 (n) -----------------   10100001

a的左边四位重置为n,同时保持右边四位不变。(a=(15&a)|(n<<4)) 举例  a=10100101   n=1    00001111(15) &10100101(a) ----------------  00000101 | 00010000 (n<<4) ---------------   00010101 搞定。 可是我们如何利用循环呢,我们如何让变量递增,写一个函数吗?循环表达式的条件还是有的。这个时候我们就可以利用define的特性,进行文本替换。 #define RightReset(a,n) (a=(240&a)|n) //重置a的右边四位为n,同时保持a的左边四位。240=二进制(11110000 #define LeftReset(a,n)  (a=(15&a)|(n<<4))//重置a的左边四位为n,同时保持a的右边边四位。15=二进制(00001111 #define GetRight(a)    (a&15)    //获取a的右边四位的值 #define GetLeft(a)    ((a&240)>>4) //获取a的左边四位的值
#include<iostream>
using namespace std;
//像这种情况只可以用define来处理了。。
#define RightReset(a,n) (a=(240&a)|n) //重置a的右边四位为n,同时保持a的左边四位。240=二进制(11110000)
#define LeftReset(a,n)  (a=(15&a)|(n<<4))//重置a的左边四位为n,同时保持a的右边边四位。15=二进制(00001111)
#define GetRight(a)    (a&15)    //获取a的右边四位的值
#define GetLeft(a)    ((a&240)>>4) //获取a的左边四位的值

int main()
{
	unsigned char a=0;
	for(RightReset(a,1);GetRight(a)<=9;RightReset(a,(GetRight(a)+1)))//利用define可以使一个变量足以解决
	{
		for(LeftReset(a,1);GetLeft(a)<=9;LeftReset(a,(GetLeft(a)+1)))
			if(GetRight(a)%3!=GetLeft(a)%3)
				cout<<"A="<<GetRight(a)<<"  B="<<GetLeft(a)<<endl;
	}
	return 0;
}

	我想问一句,这个非要两个for循环吗,看似不可避免,其实不然。有一种方法可以代替for循环的效果。就是利用除运算符和求模运算符。比如i/9和i%9,当i=80时,i/9=8,i%9=8;当i=79时,i/9=8,i%9=7;当i=78时,i/9=8,i%9=6;所以带来的效果就像是for循环,只需要在写代码的时候稍作修改即可。81=9×9;

int main()
{
	for(int i=80;i>0;i--)
	{
		if((i/9)%3!=(i%9)%3)
			cout<<"A="<<i/9+1<<"  B="<<i%9+1<<endl;
	}
return 0;
}*/

	这种利用一个变量解决循环的问题可以扩展成多重循环,只要写好判别的条件就好了,就ok。
	如何利用一个变量解决4重循环呢,当然位操作可以。可是我们要用刚才说的方法。
比如解决
  for( int i = 0; i < 5; i++ )
     for( int j = 0; j < 4; j++ )
       for( int k = 0; k < 3; k++ )
         for( int p = 0; p < 2; p++ )
	这样的循环我们定义变量var,此时var=2*3*4*5,注意循环的过程var--。越是外层的循环变化越慢,他们满足什么关系呢?i只需要循环5次就好了,j要循环4*5次,k要循环3*4*5次,p要循环2*3*4*5次。
 	故循环次数 i=var/(2*3*4) j=var/(2*3)) k=var/2   p=var
	所以i的循环要利用(var/(2*3*4))%5,j的循环要利用(var/(2*3))%4,k的循环要利用(var/(2))%3 ,p的循环要利用(var)%2。
就像刚才的象棋将帅问题,var=81, i=var/9,j=var。所以循环变量就是(var/9)%9和var%9。
代码完全可以改为

int main()
{
	for(int i=80;i>0;i--)
	{
		if(((i/9)%9)%3!=(i%9)%3)
			cout<<"A="<<i/9+1<<"  B="<<i%9+1<<endl;
	}
return 0;
}


所以掌握方法真的很重要。
转载请注明出处http://blog.csdn.net/sustliangbo/article/details/9297187


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值