洛谷 1074 [NOIP2009] 靶形数独 dfs+剪枝

29 篇文章 0 订阅
24 篇文章 0 订阅

题目:
https://www.luogu.org/problem/show?pid=1074

自己的代码70分(玄学倒搜);
改不出来看题解,长见识;

70分
正搜40,倒搜70,codevs卡时95!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int ma[10][10],ans=-1,cnt,sum[10][10];
bool can(int x,int y,int now)
{
    int fx=0,fy=0;
    if(x<=3) fx=1;
    else if(x<=6) fx=4;
    else fx=7;
    if(y<=3) fy=1;
    else if(y<=6) fy=4;
    else fy=7;
    for(int i=1;i<=9;i++)
        if(ma[x][i]==now || ma[i][y]==now) return false;
    for(int i=fx;i<=fx+2;i++)
        for(int j=fy;j<=fy+2;j++)
            if(ma[i][j]==now) return false;
    return true;
}
void dfs(int x,int tot)
{
    int fx=0,fy=0;
    if(x==0)
    {
        ans=max(ans,tot);
        return;
    }
    if(x%9==0) fx=x/9;
    else fx=x/9+1;
    fy=x-(fx-1)*9;
    if(ma[fx][fy]) dfs(x-1,tot+sum[fx][fy]*ma[fx][fy]);
    else for(int i=1;i<=9;i++)
    {
        if(can(fx,fy,i))
        {
            ma[fx][fy]=i;
            dfs(x-1,tot+sum[fx][fy]*ma[fx][fy]);
            ma[fx][fy]=0;
        }
    }
    return;
}
void ji()
{
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
            sum[i][j]+=6;
    for(int i=2;i<=8;i++)
        for(int j=2;j<=8;j++) sum[i][j]+=1;
    for(int i=3;i<=7;i++)
        for(int j=3;j<=7;j++) sum[i][j]+=1;
    for(int i=4;i<=6;i++)
        for(int j=4;j<=6;j++) sum[i][j]+=1;
    sum[5][5]++;
    return;
}
void solve()
{
    int ss[10][10];
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++) scanf("%d",&ma[i][j]);
    ji();
    dfs(81,0);
    return;
}
int main()
{
    solve();
    cout<<ans;
    return 0;
}

正解:
启发式搜索+状压;

sum_hang[i] : 第i行大于0的数的个数;
lie[i]:第i列使用的数(状压);
hang[i]:第i行使用的数(状压);
ge[i]:第i个九宫个使用的数(状压);
ma:输入;
sum:得分;
un:没有填的格子的位置;
cnt:没填的格子个数;

思路:
预处理;

跳着搜每个没有填的位置,若全搜完即得到一个解;
这里的启发函数的意义:优先搜候选数少的格子,可以减少搜索树的大小;

状压:
对于某列111111001表示7,8个空没有填数;算是卡常吧;

收获:
1. |的回溯用^;
2. 搜索可以跳着搜……;
3. 重载运算符的新姿势;

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1001,tmp=(1<<9)-1;
int sum_hang[MAXN],lie[MAXN],hang[MAXN],ge[MAXN],ma[MAXN][MAXN],sum[MAXN][MAXN];
int cnt,ans=-1,tot,num;
struct hh
{
    int x,y;
    bool operator < (hh t) const
    {
        if(sum_hang[x]==sum_hang[t.x]) return x < t.x;
        return sum_hang[x]>sum_hang[t.x];
    }
}un[MAXN];
void ji()
{
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
            sum[i][j]+=6;
    for(int i=1;i<8;i++)
        for(int j=1;j<8;j++) sum[i][j]+=1;
    for(int i=2;i<7;i++)
        for(int j=2;j<7;j++) sum[i][j]+=1;
    for(int i=3;i<6;i++)
        for(int j=3;j<6;j++) sum[i][j]+=1;
    sum[4][4]++;
    return;
}
int gets(int x,int y)
{
    return (x/3*3)+(y/3);//第几个九宫格;
}
void dfs(int cur,int tot)
{
    if(cur>cnt)
    {
        ans=max(ans,tot);
        return;
    }
    int x=un[cur].x,y=un[cur].y;
    int temp=(hang[x]|(lie[y]|ge[gets(x,y)]));//寻找可用的数,简洁迅速;
    if(temp==tmp) return;
    for(int i=1;i<=9;i++)
    {
        if(!((temp>>(i-1))&1))
        {
            int ss=1<<(i-1);
            ma[x][y]=i;
            hang[x]|=ss;
            lie[y]|=ss;
            ge[gets(x,y)]|=ss;
            dfs(cur+1,tot+ma[x][y]*sum[x][y]);
            hang[x]^=ss;//回溯用^;
            lie[y]^=ss;
            ge[gets(x,y)]^=ss;
            ma[x][y]=0;
        }
    }
    return;
}
void solve()
{
    ji();
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
        {
            scanf("%d",&ma[i][j]);
            if(ma[i][j])
            {
                num+=ma[i][j]*sum[i][j];    
                hang[i]|=(1<<(ma[i][j]-1));
                lie[j]|=(1<<(ma[i][j]-1));
                ge[gets(i,j)]|=(1<<(ma[i][j]-1));
                sum_hang[i]++;
            }
        }
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
            if(!ma[i][j]) cnt++,un[cnt].x=i,un[cnt].y=j;
    sort(un+1,un+cnt+1);//优先级排序;
    dfs(1,num);
    cout<<ans;
}
int main()
{
    solve();
    return 0;
}

好像可以用跳舞链做(也就比这份代码快一点点),但是不会,%会的dalao;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值