题目大意:给一个 (n imes 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 imes 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()
{
scanf("%lld%lld%lld",&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;
}