TopCoder ChessMetric

本文探讨了一种在棋盘上计算骑士从起点到终点不同路径数量的方法。通过动态规划技术,对比了两种算法实现方式:一种使用队列进行节点扩散,另一种采用数组直接存储路径数。后者效率更高。


问题:假如在一个棋盘上,骑士每一步只能走离自己一格的地方(包括斜线),或者走L行(如马的走法)。

比如K可以走的地方如下,包括X和L:

   .......
   ..L.L..
   .LXXXL.
   ..XKX..
   .LXXXL.
   ..L.L..
   .......

在一个size * size大的棋盘,求从start[2] 经过numMoves步走到 end[2] 的不同走法一共多少种。


这是一个动态规划的问题。一开始,我是这么想的:

可以预料到,如果要解决end[2] n步,那么必须把到 end点一步能到的所有点的n-1步走法求出来。

所以一旦size和n比较大,时间复杂度肯定很高。如果能减少不必要的计算,比如每次只对第i步能到的那些点进行处理,应该能适当降低复杂性。

为了达到这个目的,我用一个队列来保存第i+1步能到的点,并用一个[int][int][bool]的数组来进行去重。

结果就是有点慢。


然后看了一下别人的代码:

直接n * size * size 用数组解决,快了100倍。

其中一个重要的原因在于使用vector和queue等各种工具带来的开销,远比少做的那些计算耗时得多。因为每一次i的扩散,都是指数型的,所以很快就会变成遍历全部结点,而这个时候我还要处理队列,所以慢了很多。


只会想当然而不去思考,就会犯这样的错误。


vector<tuple<int, int>> getNeighbors(int x, int y, int size){
	vector<tuple<int, int>> res;
	for (int i = -1; i < 2; i++)
	{
		for (int j = -1; j < 2; j++)
		{
			if (j == 0 && i == 0)
			{
				continue;
			}
			if (x + i >= 0 && y + j >= 0 && x + i < size && y + j < size){
				res.push_back(make_pair<int, int>(x + i, y + j));
			}
		}
	}
	for (int i = 1; i < 3; i++)
	{
		if (x - i >= 0){
			if (y + i - 3 >= 0)
			{
				res.push_back(make_pair<int, int>(x - i, y + i - 3));
			}
			if (y - i + 3 < size)
			{
				res.push_back(make_pair<int, int>(x - i, y - i + 3));
			}
		}
		if (x + i < size)
		{
			if (y + i - 3 >= 0)
			{
				res.push_back(make_pair<int, int>(x + i, y + i - 3));
			}
			if (y - i + 3 < size)
			{
				res.push_back(make_pair<int, int>(x + i, y - i + 3));
			}
		}
	}
	return res;
}


long long countPath(int size, vector<int> start, vector<int>end, int numMoves){
	vector<vector<vector<long long>>> res(size, vector<vector<long long>>(size, vector<long long>(numMoves+1, 0)));//res[i][j][k]-k步到(i,j)的路有几条
	vector<vector<vector<bool>>> pushed(2, vector<vector<bool>>(size, vector<bool>(size, false)));//pushed[k][i][j]当k=stackIndex+1表示节点i,j是否已经压入栈中
	int stackIndex = 0, countnow = 1, stepnow = 1,x,y, tmpx,tmpy;
	queue<tuple<int, int>> st;
	int stx = start[0], sty = start[1];
	res[stx][sty][0] = 1;
	st.push(make_pair<int, int>(stx + 0, sty + 0));
	while (!st.empty() )
	{
		tuple<int, int> tmp = st.front();
		x = get<0>(tmp);
		y = get<1>(tmp);
		st.pop();
		countnow--;
		
		
		pushed[1 - stackIndex][x][y] = false;
		vector<tuple<int, int>> nei = getNeighbors(x, y, size);
		for (size_t i = 0; i < nei.size(); i++)
		{//对所有当前节点一步可以到的邻域节点,更新其stepnow步的路径数
			tmpx = get<0>(nei[i]), tmpy = get<1>(nei[i]);
			res[tmpx][tmpy][stepnow]+=res[x][y][stepnow-1];
			if (!pushed[stackIndex][tmpx][tmpy])
			{//如果该节点在找stepnow时没有压入过
				pushed[stackIndex][tmpx][tmpy] = true;
				st.push(nei[i]);
			}
		}


		//是否到下一层
		if (countnow == 0)
		{
			countnow = st.size();
			stepnow++;
			if (stepnow >= numMoves)
			{//最后一步的路径数单独找
				break;
			}
			stackIndex = 1 - stackIndex;
		}
	}
	//最后计算结尾节点的stepnow步数
	x = end[0], y = end[1];
	vector<tuple<int, int>> nei = getNeighbors(x, y, size);
	for (size_t i = 0; i < nei.size(); i++)
	{
		tmpx = get<0>(nei[i]), tmpy = get<1>(nei[i]);
		res[x][y][numMoves] += res[tmpx][tmpy][numMoves - 1];
	}
	return res[x][y][numMoves];
}

//别人的代码
long long ways[100][100][55];

const int dx[16] = { 1, 1, 1, 0, -1, -1, -1, 0, 2, 1, -1, -2, -2, -1, 1, 2 };
const int dy[16] = { 1, 0, -1, -1, -1, 0, 1, 1, -1, -2, -2, -1, 1, 2, 2, 1 };

long long howMany(int size, vector <int> start, vector <int> end, int nomoves) {
	int x, y, i, j;
	for (x = 0; x<100; x++) for (y = 0; y<100; y++) for (i = 0; i<55; i++) ways[y][x][i] = 0;
	int sx = start[0], sy = start[1], ex = end[0], ey = end[1];
	ways[sy][sx][0] = 1;
	for (i = 1; i <= nomoves; i++) {
		for (x = 0; x<size; x++)
		for (y = 0; y<size; y++) {
			for (j = 0; j<16; j++) {
				int nx = x + dx[j];
				int ny = y + dy[j];
				if (nx<0 || ny<0 || nx >= size || ny >= size) continue;
				ways[ny][nx][i] += ways[y][x][i - 1];
			}
		}
	}

	cout << ways[ey][ex][nomoves] << endl;
	return ways[ey][ex][nomoves];
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值