题意:
给出一个的表格,每个格子上都有一个数字。要求从左上角(1,1)出发,只能向下或向右走,走到右下角(m,n)视为结束。问有多少种走法能使得走过路径的总异或值等于。
输入:
第一行,三个数字,,,。
接下来行,每行个数字,描述表格。
输出:
一个数字,代表有多少种走法使得路径上的数字总异或值等于。
样例:
样例输入 |
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)
三种走法的总异或值均是
数据范围:
, 20
思路:
在最开始看到这道题的时候我是想也没想就写了dfs。但是可以注意到,在,20的情况下,dfs的复杂度是,最差情况甚至到了,是必定超时的,但由于自身能力不够只得放弃,这道题也就只过了50%的点。出来后问了一下这道题需要用折半搜索,将复杂度优化为。
折半搜索的核心思想是:将搜索分为前后两部分。当搜索次数到总的需要搜索次数一半时,用一个数组记录下此时前半部分有可能的所有状态;再重新开始搜索后半部分,再用一个数组记录下此时后半部分可能的所有状态;最后通过遍历两个数组,结合限制条件判断某两个状态的组合是否为合法方案。这是一种用空间换取时间的方法。
这么说着好像很难理解,那么不妨来看一下这道题应该如何将搜索拆分,以及合法答案的限制条件是什么。
由于我们是从(1,1)出发,在(m,n)结束,并且只能往下或者往右走,那么我们一定需要往下走次,往右走次。那么:
此时我们就找到了一个总搜索次数,以及两个限制条件:一共要往下走次,往右走次。再加上题目自身携带的一个限制条件:总异或值为,合法答案一共三个限制条件。
那么也就是说,我们只要把搜索拆成前半部分: ~ (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;
}