[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;
}
   

 

转载于:https://www.cnblogs.com/captain1/p/9886229.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值