题目大意:给一个 \(n\times m\) 的网格图。每个点有一权值 \(a_{i,j}\),现在要从 \((1,1)\) 走到 \((n,m)\),且仅能向右或向下,求路径经过的点的权值异或和恰好为\(k\)的方案数。
\(1 \le n,m \le 20; 0 \le k \le 10^{18}\)
题目思路:
看上去找不到什么算法去解决本题,那么遇事不决上暴搜,复杂度约为 \(2^{n+m}\),显然不能通过,并且这是CF题没部分分。
但是我们还是找不到什么解法,只好考虑优化搜索(这一步在考场上尤为重要)。
我们发现,在本题中,我们既可以从起点开始搜索,也可以从终点开始搜索。显然这是等价的。这条性质等下将用到。
现在引入一个概念:双向搜索/折半搜索/Meet in the middle。顾名思义,其方法是分别从起点和终点开始,做一半路程的搜索,并保存下结果,最后进行合并。这个方法的原理就是上文从起点搜和从终点搜等价的性质。
于是,搜索的时间复杂度由 \(2^{n+m}\) 变为 \(2\times 2^{\frac{n+m}{2}}\),可以接受。
当然,合并答案也是很重要的一步。考虑如何快速得出答案,我们使用\(map\)来保存每个中间点的答案,从起点搜时记录,从终点搜时取出即可。
上代码
#include<bits/stdc++.h>
using namespace std;
map<long long,int> f[30][30];
long long n,m,k,z[30][30],a[30][30],ans,t;
void q_dfs(int x,int y,long long kk)
{
if (x>n||y>m) return ;
if (x+y==((n+m+2)>>1))
{f[x][y][kk]++;return ;}
q_dfs(x+1,y,kk^a[x+1][y]);
q_dfs(x,y+1,kk^a[x][y+1]);
}
void h_dfs(int x,int y,long long kk)
{
if (x<1||y<1) return ;
if (x+y==((n+m+2)>>1))
{
if (f[x][y].find(k^(kk^a[x][y]))==f[x][y].end()) return ;
ans+=f[x][y][k^(kk^a[x][y])];
return ;
}
h_dfs(x-1,y,kk^a[x-1][y]);
h_dfs(x,y-1,kk^a[x][y-1]);
}
int main()
{
cin>>n>>m>>k;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
scanf("%lld",&a[i][j]);
f[i][j].clear();
}
q_dfs(1,1,a[1][1]);
h_dfs(n,m,a[n][m]);
cout<<ans<<endl;
return 0;
}```