西山居3.22笔试第一题(折半搜索)

题意:

给出一个m*n的表格,每个格子上都有一个数字。要求从左上角(1,1)出发,只能向下或向右走,走到右下角(m,n)视为结束。问有多少种走法能使得走过路径的总异或值等于k

输入:

第一行,三个数字,nmk

接下来m行,每行n个数字,描述表格。

输出:

一个数字,代表有多少种走法使得路径上的数字总异或值等于k

样例:

样例输入

3 2 15

1 2 4

2 4 8

样例输出
3

样例解释:

一共三种走法,分别是:

(1,1)→(1,2)→(1,3)→(2,3)

(1,1)→(1,2)→(2,2)→(2,3)

(1,1)→(2,1)→(2,2)→(2,3)

三种走法的总异或值均是 1 xor 2 xor 4 xor 8 = 15

数据范围:

0  \leq  m,n  \leq  20

思路:

在最开始看到这道题的时候我是想也没想就写了dfs。但是可以注意到,在0\leqm,n\leq20的情况下,dfs的复杂度是O(2^{n+m}),最差情况甚至到了O(2^{40}),是必定超时的,但由于自身能力不够只得放弃,这道题也就只过了50%的点。出来后问了一下这道题需要用折半搜索,将复杂度优化为O(2^{\frac{m+n}{2}+1})

折半搜索的核心思想是:将搜索分为前后两部分。当搜索次数到总的需要搜索次数一半时,用一个数组记录下此时前半部分有可能的所有状态;再重新开始搜索后半部分,再用一个数组记录下此时后半部分可能的所有状态;最后通过遍历两个数组,结合限制条件判断某两个状态的组合是否为合法方案。这是一种用空间换取时间的方法。

这么说着好像很难理解,那么不妨来看一下这道题应该如何将搜索拆分,以及合法答案的限制条件是什么。

由于我们是从(1,1)出发,在(m,n)结束,并且只能往下或者往右走,那么我们一定需要往下走m-1次,往右走n-1次。那么:

此时我们就找到了一个总搜索次数n+m-2,以及两个限制条件:一共要往下走m-1次,往右走n-1次。再加上题目自身携带的一个限制条件:总异或值为k,合法答案一共三个限制条件。

那么也就是说,我们只要把搜索拆成前半部分:1 ~ (m+n-2)/2 ,以及后半部分:(m+n-2)/2+1 ~ m+n-2 ,并用两个数组s1与s2分别记录两个部分的所有状态,最后判断前半部分任一状态s1[i]与后半部分任一状态s2[j]是否满足三个条件,可以组成合法答案即可。

可以发现,当前半部分某个状态s1[i]与后半部分某个状态s2[j]满足横向操作数总和=m-1、纵向操作数总和=n-1,s1[i]与s2[j]两个状态记录的是两部分搜索到同一个格子的状态。因为当起点确定,横向与纵向操作次数确定,那么此时搜索到的格子也能被确定。也就是说,当满足状态s1[i]的横向操作数+状态s2[j]的横向操作数=m-1、状态s1[i]的纵向操作数+状态s2[j]的纵向操作数=n-1时,s1与s2表示的是分别从起点、终点开始搜索,到达某一个格子a[x][y]时的前、后部分总异或值,那么此时只要判断这两个状态记录的总异或值是否为k,就可以判定它是否为合法答案了。

当然了,后半部分的搜索要做一点的小小的变化。为了方便,我们将后半部分的搜索转变为从(m,n)开始,只能向上走或者向左走。并且可以注意到,我们在处理前半部分搜索时,搜索到a[x][y]并且要记录状态时,s1.val记录的总异或值是已经异或了a[x][y]的。如果s2.val记录的总异或值也异或a[x][y],那么在进行判断  s1[i].val ^ s2[j].val == k  时,会由于异或两次a[x][y]而使得a[x][y]被抵消。所以在后半部分记录s2.val的时候,我再异或了一次a[x][y]使得s2.val记录的异或值不含a[x][y]。当然了,这个操作也可以放在前半部分搜索记录s1.val的时候完成。

最后只要在搜索达到次数时,记录一下此时的总异或值、横向移动次数、纵向移动次数,用于判断合法方案就可以了。

代码:

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

int a[100][100] = { 0 }, n, m, k, ans = 0, cnt1 = 0, cnt2 = 0;

typedef struct States
{
	int val = 0, t = 0, l = 0;    //val保存总异或值,t记录横向移动次数,l记录纵向移动次数
};

States s1[200005] = { 0 }, s2[200005] = { 0 };

void dfs1(int x, int y, int temp)    //此时搜索的位置在(x,y),证明已经经过了x-1次横向操作与y-1次纵向操作
{
	if ((x - 1) + (y - 1) == (m + n - 2) / 2) {    //当前操作数达到需要的总次数m+n-2的一半,记录当前状态   
		s1[cnt1].t = x - 1;    //记录横向操作数
		s1[cnt1].l = y - 1;    //记录纵向操作数
		s1[cnt1++].val = temp;    //记录总异或值
		return;
	}
	else {    //如果当前操作数还不到需要的总次数一半,继续搜索
		if (x < m) dfs1(x + 1, y, temp ^ a[x + 1][y]);
		if (y < n) dfs1(x, y + 1, temp ^ a[x][y + 1]);
		return;
	}
}

void dfs2(int x, int y, int temp)
{
	if ((m - x) + (n - y) == (m + n - 2) - (m + n - 2) / 2) {
		s2[cnt2].t = m - x;
		s2[cnt2].l = n - y;
		s2[cnt2++].val = temp ^ a[x][y];    //这里有些特殊,要再异或一次a[x][y]
		return;
	}
	else {
		if (x > 1) dfs2(x - 1, y, temp ^ a[x - 1][y]);
		if (y > 1) dfs2(x, y - 1, temp ^ a[x][y - 1]);
		return;
	}
}

int main()
{
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++)
		for (int j = 1; j <= n; j++) cin >> a[i][j];

	dfs1(1, 1, a[1][1]);
	dfs2(m, n, a[m][n]);

	for (int i = 0; i < cnt1; i++)
		for (int j = 0; j < cnt2; j++)
			if ((s1[i].t + s2[j].t == m - 1) && (s1[i].l + s2[j].l == n - 1))    //如果s1[i]与s2[j]记录的是同一个格子的状态
                if ((s1[i].val ^ s2[j].val) == k) ans++;    //如果总异或值=k,满足三个限制条件,那么他就是一个合法答案
	cout << ans;
}
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我可以帮你解答关于折半查找的C语言习折半查找,也叫二分查找,是一种效率很高的查找算法。它要求被查找的序列已经排序。具体的实现可以参考下面的C语言代码: ```c #include <stdio.h> int binary_search(int arr[], int n, int x) { int left = 0, right = n - 1; while (left <= right) { int mid = (left + right) / 2; if (arr[mid] == x) { return mid; } else if (arr[mid] > x) { right = mid - 1; } else { left = mid + 1; } } return -1; // 表示未找到 } int main() { int arr[] = {2, 4, 6, 8, 10}; int n = sizeof(arr) / sizeof(int); int x = 6; int idx = binary_search(arr, n, x); if (idx != -1) { printf("%d is found at index %d.\n", x, idx); } else { printf("%d is not found.\n", x); } return 0; } ``` 在上面的代码中,函数`binary_search`接收三个参数:一个整数数组`arr`、数组的长度`n`和要查找的目标值`x`。它返回目标值在数组中的下标,如果未找到则返回-1。 函数中使用了两个指针`left`和`right`来表示当前查找区间的左右端点。在每次循环中,计算中间位置`mid`,判断目标值与中间值的大小关系,如果相等则直接返回中间位置,如果目标值比中间值小,则缩小区间的右端点,否则缩小区间的左端点,直到找到目标值或区间缩小到空集为止。 在主函数中,定义了一个大小为5的整数数组,然后调用`binary_search`函数来查找6在数组中的下标。如果找到,则输出目标值和下标,否则输出未找到的提示信息。 希望这个代码能够帮到你。如果还有其他问,可以继续问我哦。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值