骗分大法——深搜

9.15

伤心消消忧

这种在一个图形里一步一步操作,求最少步骤的题,一般是搜索。显然不能DP,不能启发式,数据范围又小,所以暴力深搜。

先保存当前图的状态,以便后来回溯。以此图为状态,枚举每一个点,移动。移动后模拟方块下落和方块消除,直到方块不能再消除,方块下落才停止。

剪枝:

  1. 交换两个颜色相同的块没有意义

  1. 一个块的左边是非空块时不需要考虑左移,因为会和之前的块右移重复,即只有当左块为空时才左移

  1. 根据题目优先度的排序,可以知道,右移优先于左移,所以在dfs时先考虑右移

    上码

    ​
    #include<bits/stdc++.h>
    using namespace std;
    int n,mp[6][8],ans[6][3],tmp[6][6][8],xiao[6][8];
    ​
    inline bool pd(){//底下一层没有块
        for(int i=0;i<5;++i)
            if(mp[i][0])    return 0;
        return 1;
    }
    ​
    inline void update(){
        for(int i=0;i<5;++i){
            int wow=0;
            for(int j=0;j<7;++j){
                if(!mp[i][j])   ++wow;
                else{
                    if(!wow)continue;
                    mp[i][j-wow]=mp[i][j];
                    mp[i][j]=0;
                }
            }
        }
        return ;
    }
    ​
    bool disa(){
        bool flag=0;
        for(int i=0;i<5;++i)
            for(int j=0;j<7;++j){
                if(!mp[i][j])   continue;
                if(i<3)//消除的块需要标记,之后再清除
                    if( mp[i][j]==mp[i+1][j]&&mp[i+2][j]==mp[i+1][j]){
                        flag=1;xiao[i][j]=xiao[i+1][j]=xiao[i+2][j]=1;
                    }
                if(j<5)
                    if( mp[i][j]==mp[i][j+1]&&mp[i][j+2]==mp[i][j]){
                        flag=1;xiao[i][j]=xiao[i][j+1]=xiao[i][j+2]=1;
                    }
            }
        for(int i=0;i<5;++i)
            for(int j=0;j<7;++j){
                if(xiao[i][j]==1){
                    mp[i][j]=0;
                    xiao[i][j]=0;
                }
            }
        return flag;
    }
    void move(int x,int y,int f){
        swap(mp[x][y],mp[x+f][y]);
        update();
        while(disa())   update();//下落,直到不能消除
        return ;
    }
    void dfs(int dep){
        if(pd()){
            for(int i=1;i<dep;++i)
                printf("%d %d %d\n",ans[i][0],ans[i][1],ans[i][2]);
            exit(0);
        }
        if(dep==n+1)    return;
        memcpy(tmp[dep],mp,sizeof(mp));//用tmp记录每一步执行前的状态,方便回溯
        for(int i=0;i<5;++i)
            for(int j=0;j<7;++j){
                if(mp[i][j]){
                    if(i<4&&mp[i+1][j]!=mp[i][j]){
                        move(i,j,1);//移动并记录,用于输出答案
                        ans[dep][0]=i;
                        ans[dep][1]=j;
                        ans[dep][2]=1;
                        disa();
                        dfs(dep+1);
                        memcpy(mp,tmp[dep],sizeof(tmp[dep]));
                    }
                    if(i){
                        if(mp[i-1][j]==0){//只有当左边没有块时才左移
                            move(i,j,-1);
                            ans[dep][0]=i;
                            ans[dep][1]=j;
                            ans[dep][2]=-1;
                            disa();
                            dfs(dep+1);
                            memcpy(mp,tmp[dep],sizeof(tmp[dep]));
                        }
                    }   
                }
            }
        return;
    }
    int main(){
        scanf("%d",&n);
        for(int i=0;i<5;++i)
            for(int j=0;j<=7;++j){
                scanf("%d",&mp[i][j]);
                if(!mp[i][j])   break;
            }
        dfs(1);
        puts("-1");
        return 0;
    }
    /*
    2 
    1 0
    1 0
    2 2 0
    1 0
    2 0
    */
    
    ​

数独

同是深搜+模拟,这是高级版数独,需要优化.

优化:

  1. 状态压缩,用三个数组维护每行、每列、每个九宫格能填的数字,对于点 (i,j) tmp=h[i]&l[j]&g[GG(i,j)];,tmp就是能填的数,如(000001011)能填的数就是1、2、4.所以需要预处理log(2).

  2. 和正常人做数独一样,每次需要找到能填的数最少(>=1)的格子填(深搜前for循环找到这个格子,所以需要预处理每个状态(二进制数)中的1的个数),然后选择它的lowbit开始搜索

好码好码好码好码

#include<bits/stdc++.h>
#define fuck puts("fuck");
#define GG(x,y) (3*((x+2)/3-1)+(y+2)/3)
using namespace std;
int h[10],l[10],g[10],mp[10][10],can[10][10],lg[533],ww[533];
char s[90];
​
int kepu(int x){
    int ret=0;
    for(;x;x>>=1)
        if(x&1) ++ret;
    return ret;
}
void print(){
    for(int i=1;i<=9;++i)
        for(int j=1;j<=9;++j)
            printf("%d",mp[i][j]);
    putchar('\n');
}
bool dfs(){
    int tmp,nnn,minn=10,posx,posy;
    for(int i=1;i<=9;++i)
            for(int j=1;j<=9;++j){
                if(mp[i][j])    continue;
                tmp=h[i]&l[j]&g[GG(i,j)];
                nnn=ww[tmp];
                if(nnn<minn)    {
                    minn=nnn;
                    posx=i;
                    posy=j;
                }
            }
    if(minn==0) return 0;
    if(minn==10){//都填满了,结束
        print();
        return 1;
    }   
    tmp=h[posx]&l[posy]&g[GG(posx,posy)];
    for(;tmp;tmp=tmp-(tmp&(-tmp))){
            int i=tmp&(-tmp);//标记选择
            mp[posx][posy]=lg[i];
            h[posx]^=i;
            l[posy]^=i;
            g[GG(posx,posy)]^=i;
            if(dfs())   return 1;//回溯
            mp[posx][posy]=0;
            h[posx]^=i;
            l[posy]^=i;
            g[GG(posx,posy)]^=i;
        }
    return 0;
}
void init(){
    for(int i=1;i<=9;++i)
        h[i]=l[i]=g[i]=511;//(111111111)2
    for(int i=1,j=1;i<=512;i<<=1,++j)
        lg[i]=j;//求1在第几个,如lg[16]=4(因为16=(1000)2)
    for(int i=1;i<=511;++i)
        ww[i]=kepu(i);//二进制下有多少个数码是1
}
int main(){
    scanf("%s",s+1);
    while(s[1]!='e'){
        init();
        for(int i=1;i<=9;++i){
            for(int j=1;j<=9;++j){
                if(s[(i-1)*9+j]!='.'){
                    mp[i][j]=s[(i-1)*9+j]-'0';
                    h[i]=h[i]^(1<<(mp[i][j]-1));
                    l[j]=l[j]^(1<<(mp[i][j]-1));
                    g[GG(i,j)]=g[GG(i,j)]^(1<<(mp[i][j]-1));
                }
                else mp[i][j]=0;
            }
        }
        dfs();
        scanf("%s",s+1);
    }
}
/*
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
end
*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值