搜索II 难度:提高

                                                                                                                ——Frosty_Jackal 于9.14

                                                                                                                                  

由于‘N’较小,考虑用二进制压缩来处理。

令一个长度为9 的 01 数字串 中,1表示该数位上没填数,0表示填了。为什么用1表示没填?方便用lowbit函数找到这个‘1’值,从而找到要去填那个数。

由题意 对一个坐标所在九宫格可以由它的行,列确定:grid(x,y)=(x/3)*3 + y/3

由于是多组数据,预处理就显得尤为重要:

  1. 由题意,优先找到哪个坐标能填合法数字最少,去填。于是处理出每个状态有多少个1:
 for(int i=0;i<(1<<SIZE); ++ i ) 
        for(int j=i;j;j-=lowbit(j))
        {
            cnt[i]++;
        }
  1. 对于一个lowbit运算,我们取出的是1所在的位置,怎么通过位置找到实际对应的填数?

即:

for(int i=0;i<SIZE;++i) num[(1<<i)]=i;

为了方便回溯,填数的时候用异或操作!

inline void filp(int x,int y,int z)
{
    h[x]^=(1<<z);
    l[y]^=(1<<z);
    grid[getgrid1(x,y)]^=(1<<z);
}

输入输出采用这种格式最好!

for(int i=0;i<SIZE;++i)
for(int j=0;j<SIZE;++j)
sta[i][j]=s[i*9+j];

在输入中可以记tot=‘.’的总数,

DFS的过程可以以tot作为每一层状态转移的标志。

代码:

int SIZE=9,c;
char s[MAXN];char sta[21][21];
bool vi[12][12];
int h[11],l[11],grid[11],cnt[(1<<11)],num[(1<<11)];
inline int lowbit(int x){ return  x& ( -x );}
 
inline int getgrid1(int x,int y)
{
    return (x/3)*3+y/3;
}
 
inline void filp(int x,int y,int z)
{
    h[x]^=(1<<z);
    l[y]^=(1<<z);
    grid[getgrid1(x,y)]^=(1<<z);
}
 
bool dfs(int now)
{
    if(now==0)  return true;
    int temp=10,x,y;
    for(int i=0;i<SIZE;++i)
        for(int j=0;j<SIZE;++j)
        {
            if(sta[i][j]!='.') continue;
            int val=h[i] & l[j] & grid[getgrid1(i,j)];
            if(!val) return 0;
            if(cnt[val]<temp)
            {
                temp=cnt[val];
                x=i,y=j;
            }
        }
    int val=(h[x] & l[y] & grid[ getgrid1(x,y)]);
    for(;val;val-=lowbit(val))
    {
        int je=lowbit(val);
        int c=num[je];
        sta[x][y]=c+'1';
        filp(x,y,c);
        if(dfs(now-1)) return 1;
        filp(x,y,c);
        sta[x][y]='.';
    }
    return 0;
}
 
int main()
{   
    for(int i=0;i<(1<<SIZE); ++ i ) 
        for(int j=i;j;j-=lowbit(j))
        {
            cnt[i]++;
        }
    for(int i=0;i<SIZE;++i)  num[(1<<i)]=i;
    while(scanf("%s",s) && s[2]!='d')
    {
        int tot=0;
        for(int i=0;i<SIZE;++i)
            for(int j=0;j<SIZE;++j)
            {
                sta[i][j]=s[i*9+j];
            }
        for(int i=0;i<SIZE;++i) h[i]=l[i]=grid[i]=(1<<9)-1;
        for(int i=0;i<SIZE;++i)
            for(int j=0;j<SIZE;++j)
            {
                if(sta[i][j]!='.')
                {
                    filp(i,j,sta[i][j]-'1');    
                }
                else tot++;
            }
        dfs(tot);
        for(int i=0;i<SIZE;++i)
        for(int j=0;j<SIZE;++j)
        {
                s[i*9+j]=sta[i][j];
        }
        puts(s);
    }
    return 0;
}
 
/*
.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
expected answer:
527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936
*/

 

 

是一个深搜为主题的大模拟。十分考验代码技巧。

首先是消除操作,不能逢3就消除,这样对于5个同色的情况便不能全部消除,应当先全部标记后统一消除。

inline bool remove()
{
    int flag=0;
    for(int i=1;i<=5;++i)
        for(int j=1;j<=7;++j)
        {
            if(i-1>=1 && i+1<=5 && map[i-1][j]==map[i][j] &&  map[i][j]==map[i+1][j] && map[i][j])
            {
                xiao[i-1][j]=xiao[i][j]=xiao[i+1][j]=1;flag=1;
            }
            if(j-1>=1 && j+1<=7 && map[i][j-1] == map[i][j] && map[i][j]==map[i][j+1] && map[i][j])
            {
                xiao[i][j-1]=xiao[i][j]=xiao[i][j+1]=1;flag=1;
            }
    }
    if(!flag) return 0;
    for(int i=1;i<=5;++i)
        for(int j=1;j<=7;++j)
        {
            if(xiao[i][j])
            {
                xiao[i][j]=0;
                map[i][j]=0;
            }
        }
    return 1;
}

其次是下落操作。

inline void update()
{
    for(int i=1;i<=5;++i)
    {
        int x=0;
        for(int j=1;j<=7;++j)
        {
            if(!map[i][j]) x++;
            else
            {
                if(!x) continue;
                map[i][j-x]=map[i][j];
                map[i][j]=0;
            }
        }
    }
}

然后是复制操作,用于回溯。

for(int i=1;i<=5;++i)
    {
        for(int j=1;j<=7;++j)
        last[x][i][j]=map[i][j];
    }
}

由于last的第一维度是步数,于是进一步深搜时,步数+1,不会被覆盖掉。

检查是否全部消除

inline bool check()
{
    for(int i=1;i<=5;++i)
        if(map[i][1]) return 0;
    return 1;
}

左右移动操作:

注意,若移动并掉落后,可能会同色,此时需要不断进行消除和掉落操作保证状态图合法。

void move(int i,int j,int x)
{
    int tmp=map[i][j];
    map[i][j]=map[i+x][j];
    map[i+x][j]=tmp;
    update();
    while(remove()) update();
}

搜索中的剪枝:

  1. 对于一个方块,若它左边有方块则不再往左边搜索,因为当枚举完左边那个方块时,已经通过搜索证明了往左搜的情况是不合法的。
  2. 在(1)对向左边搜的限制下,当右边同色,不再向右搜。

代码:

int n,map[MAXN][MAXN];
int ans[MAXN][38];
int last[MAXN][MAXN][MAXN];
bool xiao[MAXN][MAXN];
 
inline void update()
{
    for(int i=1;i<=5;++i)
    {
        int x=0;
        for(int j=1;j<=7;++j)
        {
            if(!map[i][j]) x++;
            else
            {
                if(!x) continue;
                map[i][j-x]=map[i][j];
                map[i][j]=0;
            }
        }
    }
}
inline bool remove()
{
    int flag=0;
    for(int i=1;i<=5;++i)
        for(int j=1;j<=7;++j)
        {
            if(i-1>=1 && i+1<=5 && map[i-1][j]==map[i][j] &&  map[i][j]==map[i+1][j] && map[i][j])
            {
                xiao[i-1][j]=xiao[i][j]=xiao[i+1][j]=1;flag=1;
            }
            if(j-1>=1 && j+1<=7 && map[i][j-1] == map[i][j] && map[i][j]==map[i][j+1] && map[i][j])
            {
                xiao[i][j-1]=xiao[i][j]=xiao[i][j+1]=1;flag=1;
            }
    }
    if(!flag) return 0;
    for(int i=1;i<=5;++i)
        for(int j=1;j<=7;++j)
        {
            if(xiao[i][j])
            {
                xiao[i][j]=0;
                map[i][j]=0;
            }
        }
    return 1;
}
 
void move(int i,int j,int x)
{
    int tmp=map[i][j];
    map[i][j]=map[i+x][j];
    map[i+x][j]=tmp;
    update();
    while(remove()) update();
}
 
inline void copy(int x)
{
    for(int i=1;i<=5;++i)
    {
        for(int j=1;j<=7;++j)
        last[x][i][j]=map[i][j];
    }
}
 
inline bool check()
{
    for(int i=1;i<=5;++i)
        if(map[i][1]) return 0;
    return 1;
}
 
void dfs(int x)
{
    if(check())
    {
        for(int i=1;i<=n;++i)
        {
            if(i!=1) printf("\n");
            for(int j=1;j<=3;++j)
            printf("%d ",ans[i][j]);
        }
        exit(0);
    }
    if(x==n+1) return ;
    copy(x);
    for(int i=1;i<=5;++i)
        for(int j=1;j<=7;++j)
        {
            if(!map[i][j]) continue;
            if(map[i][j]!=map[i+1][j] && i+1<=5)
            {
                move(i,j,1);
                ans[x][1]=i-1;ans[x][2]=j-1;ans[x][3]=1;
                dfs(x+1);
                for(int i=1;i<=5;++i)
                {
                    for(int j=1;j<=7;++j)
                    {
                        map[i][j]=last[x][i][j];
                    }
                }
                ans[x][1]=-1;ans[x][2]=-1;ans[x][3]=-1;
            }
            if(map[i-1][j]==0 && i-1>=1)
            {
                move(i,j,-1);
                ans[x][1]=i-1;ans[x][2]=j-1;ans[x][3]=-1;
                dfs(x+1);
                for(int i=1;i<=5;++i)
                {
                    for(int j=1;j<=7;++j)
                    {
                        map[i][j]=last[x][i][j];
                    }
                }
                ans[x][1]=-1;ans[x][2]=-1;ans[x][3]=-1;
            }
        }
     
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=5;++i)
        for(int j=1;j<=8;++j)
        {
            int x;  scanf("%d",&x);
            if(x==0) break;
            map[i][j]=x;
        }
    memset(ans,-1,sizeof(ans));
    dfs(1);
    puts("-1");
    return 0;
}
 
/*
3
1 0
2 1 0
2 3 4 0
3 1 0
2 4 3 4 0
 
expected answer:
2 1 1
3 1 1
3 0 1
*/

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值