题解 P5023 【填数游戏】
这里提供一种非常快的暴力打表算法
2s打出(8,9)
怎么做到的呢?首先我的思路受了楼上某位大佬的启发,他的题解里说了如下两条规律:
一个棋盘是合法的,当且仅当
- 对于每一条自左下到右上的对角线,填数是非严格单调递减的。
- 如果(i-1,j)和(i,j-1)的填数相同,那么以(i,j)为左上角、以(n,m)为右下角的子矩阵内所有对角线内的填数各自相等。
(以上引自那位大佬的题解)
第一点是再显然不过了,但是第二点怎么证明呢?
如图,对于从(1,1)出发的两条路线,第一条(粉色)第一步是向右,而第二条(黄色)第一步是向下,所以
由此,可以得出一个O(2^(nm)×nm)的算法,即先暴搜出一种填法,然后设
然后再扫描一遍整个棋盘,检查上述第2点即可。
但是这样还是太慢了,于是我们可以在填数的时候就处理出a和b,然后每填一个数就判断一下与这个格子有关的b的值,如果不符合就直接返回,这样不仅省去了填完棋盘后的
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 13
#define M 13
int g[N][M],a[N][M],ans,n,m;
bool b[N][M];
bool check(int x,int y)
{
a[x][y]=a[x+1][y]|(g[x][y]<<(n-x));
if(y==m)
b[x][y]=true;
else
b[x][y]=b[x][y+1]&&(a[x][y+1]>>1)==a[x+1][y];
if(x<n&&y>1&&g[x][y]==g[x+1][y-1]&&!b[x+1][y])return false;
return true;
}
void dfs(int x,int y)
{
if(y<1)return dfs(x-1,m);
if(x<1)
{
ans++;
return;
}
if(x==n||y==1||g[x+1][y-1]==1)
{
g[x][y]=1;
if(check(x,y))dfs(x,y-1);
}
g[x][y]=0;
if(check(x,y))dfs(x,y-1);
}
int main()
{
scanf("%d%d",&n,&m);
dfs(n,m);
printf("%d",ans);
return 0;
}