GDOI2016模拟8.21新数独

题目
下面是一个没有数字,只有大小关系(没错!那些尖角都是“大于”符号!)的数独:
这里写图片描述
除了大小关系外(注意相邻格子不能相同),还需要满足通常的数独规则:

l 每个格子都是1~9 的数字

l 每行都是1~9的排列

l 每列都是1~9的排列

l 每个3*3的子矩阵(上图中用粗线隔开,一共有3*3个这样的子矩阵)都是1~9的排列

为了美观,每个3*3子矩阵的所有12对相邻格子的大小关系都将给出。

这道题,我们可以注意到有朴素数独的限制,例如每行都是1~9的排列
, 每列都是1~9的排列,但光有这个,搜索还是很慢。

这里,我打算所以一下一开始做题时我对这道题的看法:

那些大于小于号,假设我们现在要确定9要放哪里,那么画出来的图,对应9的位置,所有符号都是它指向外面的(就是它周围的都是他指向外面的大于号),这简直是9在大吼着:“我在这里…”…于是我就顺从了..

对于这个发现,我们可以从大到小枚举先放哪些数,放完9个位置后再放其他的,若存在a<b,则b连a一条边,并将a入度+1。由于我们每次放的都是现存最大的数,那么我们按小九宫格为单位,每个九宫格里找到入度为0的位置,将数填进去,然后消除该位置对周围位置的影响(类似拓扑排序,将它连出去的位置入度-1)。

加上这个优化,我们就可以飞快的跑过了….(说实话,刚提交代码,连running都没有看到..)

好了,我们来讨论一下为甚么这个剪枝这么强力?

由于保证存在唯一解,对于相同决策,即同一个数(指一个小九宫格内)能填的位置(入度为0)的地方不会超过5,而当你选择了一个位置填入的时候,说明你周围的位置都是先前不可能决策到的(这个可以证明前者),而当前存在多个决策时,则说明以后的决策会少那么多(我们可以想着一个正向决策即由大取到小,一个反向决策即由小取到大)【而且加上数独的限制,均摊相同决策不超过3个】均摊下来,若要跑得最慢的话,最大决策层在中间,只会有3个相同决策,其余两端决策数会依次递减,均摊 69 (我估计的,这也可以接受)而实际上非常小,通常由于唯一解,第一次就会直接搜到答案。

贴代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 10
using namespace std;
int in[82],g[82],a[1000][2],d[82],help[N],b[N];
bool bzh[9][9],bzz[9][9];
void ins(int x,int y){
    static int sum=0;
    a[++sum][0]=y,a[sum][1]=g[x],g[x]=sum;
}
void did1(int x){
    static char c;
    for (int j=1;j<=3;j++){
        for (int k=1;k<=2;k++){
            scanf(" %c",&c);
            if (c=='>')
                in[x+1]++,ins(x,x+1);
            else
                in[x]++,ins(x+1,x);
            x++;
        }
        x++;
    }
}
void did2(int x){
    static char c;
    for (int j=1;j<=3;j++)
        for (int k=1;k<=3;k++){
            scanf(" %c",&c);
            if (c=='v')
                in[x]++,ins(x-9,x);
            else
                in[x-9]++,ins(x,x-9);
            x++;
        }
}
void init(){
    static int x;
    for (int i=1;i<=3;i++){
        x=(i-1)*27+1;
        did1(x);
        x+=9;
        did2(x);
        did1(x);
        x+=9;
        did2(x);
        did1(x);
    }
}
int did(int x){
    return !x?9:x;
}
bool jian(int x,int y){
    return !bzz[x][did(y%9)]&&!bzh[x][y/9+(y%9>0)];
}
void change(int x,int y){
    bzz[x][did(y%9)]=bzh[x][y/9+(y%9>0)]=1;
    for (int i=g[y];i;i=a[i][1])
        in[a[i][0]]--;
}
void changeback(int x,int y){
    bzz[x][did(y%9)]=bzh[x][y/9+(y%9>0)]=0;
    for (int i=g[y];i;i=a[i][1])
        in[a[i][0]]++;
}
bool dfs(int x,int y){
    int s;
    if (x>9){
        x=1,y--;
    }
    if (!y)return 1;
    s=help[x];
    ++x;
    for (int i=1;i<=9;i++)
        if (!d[s+b[i]]&&jian(y,s+b[i])&&!in[s+b[i]]){
            d[s+b[i]]=y;
            change(y,s+b[i]);
            if (dfs(x,y))return 1;
            changeback(y,s+b[i]);
            d[s+b[i]]=0;
        }
    return 0;
}
void work(){
    help[1]=1,help[2]=4,help[3]=7,help[4]=28,help[5]=31,help[6]=34,help[7]=55,help[8]=58,help[9]=61;
    b[2]=1,b[3]=2,b[4]=9,b[5]=10,b[6]=11,b[7]=18,b[8]=19,b[9]=20;
    dfs(1,9);
}
void write(){
    for (int i=1;i<=81;i++){
        if (!(i%9))printf("%d\n",d[i]);
        else
        printf("%d ",d[i]);
    }
}
int main(){
    init();
    work();
    write();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值