[USACO11NOV]二进制数独Binary Sudoku
这道题是很好的一道IDA*练习题。
首先我们先确定搜索的框架,我们要求的是用最少的修改次数使得所有的行,列,宫之内都有偶数个1,最直观的想法显然是先预处理出有奇数个1的行,列,宫,之后枚举每一个点,如果这个点在奇数个1的行/列/宫之中就开始修改,继续搜索。修改整个数独之后判定,回溯。
这个正确性是没问题的,但是你难以通过有效的手段减少搜索树大小(比如从1多的开始搜?),这样肯定会超时(想想靶型数独)。
于是我们引入新的操作:ID(迭代加深)搜索!
ID的原理是,有可能搜索树某些分枝非常的深,但里面并没有你要的解,如果你率先进入的话,就会浪费大量的时间在里面。有可能解的深度并不是很大,所以我们可以每次设定一个搜索的深度,如果超过这个深度就返回,之后继续加大深度搜索,所以叫迭代加深。
但是这样我们依然可能很慢,所以我们再加上一个,A*算法!
A*算法简单来说,就是设计一个估价函数,估计未来的搜索情况,选择当前估计最好的一个搜索方向去搜索。估计的值越接近实际值,搜索效率越高。估价函数有一个原则,就是估计的值不可以大于实际值,否则就会导致得到错误的答案。(因为估计值过大掩盖了真实的解)而且我们无需担心正确性,因为A*算法只是优先进入分支,在进行一段时间搜索之后,原来被忽略的分支又将进入计算。A*的估价函数如果一直是0,那么他就相当于是一个无优化的搜索,所以说A*算法的效率一般来讲是高于普通搜索的,至于高多少就要看设计的估价函数的高明之处了。
A*的实现比较复杂,但与ID结合起来成为IDA*就比较好实现了,它其实可以被视为剪枝,当当前的值加上估价值比当前的最大深度更大,就直接返回,相当于剪枝了。在这道题中,我们要做的就是从小到大枚举搜索深度(就是修改的次数),在每次设计一个估价函数,根据上面的原则,我们修改之后,至多需要max有奇数个1的(行,列,宫)次修改,每次估价函数设为三者最大值即可。
这样搜索快到飞起,也有可能是本题数据比较水……
看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<set> #include<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 10005; const int INF = 1000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } int b,c,r,row[11],col[11],blo[11],maxd; char s[11]; int B(int x,int y) { return ((x - 1) / 3 * 3 + (y - 1) / 3) + 1; } int h() { return max(r,max(b,c)); } int cal(int x) { return (!x) ? -1 : 1; } void change(int i,int j) { row[i] ^= 1,col[j] ^= 1,blo[B(i,j)] ^= 1; r += cal(row[i]),c += cal(col[j]),b += cal(blo[B(i,j)]); } void dfs(int x,int y,int d) { if(d > maxd) return; if(x == 9 && y == 9) { rep(i,1,9) if(row[i] | col[i] | blo[i]) return; printf("%d\n",maxd),exit(0); } if(row[x] | col[y] | blo[B(x,y)]) { change(x,y); if(d + 1 + h() <= maxd) (y == 9) ? dfs(x+1,1,d+1) : dfs(x,y+1,d+1); change(x,y); if(d + h() <= maxd) (y == 9) ? dfs(x+1,1,d) : dfs(x,y+1,d); } else (y == 9) ? dfs(x+1,1,d) : dfs(x,y+1,d); } int main() { rep(i,1,9) { scanf("%s",s+1); rep(j,1,9) if(s[j] == '1') change(i,j); } for(maxd = 0;;maxd++) dfs(1,1,0); return 0; }