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

来源:互联网 发布:小米手机淘宝 编辑:程序博客网 时间:2024/06/05 04:11

题目:
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;