搜索练习

1、靶形数独(传送门

就是poj3074的数独加上一个判断就好了,注意位运算的细节。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int row[10],col[10],tub[20];
int cnt[600],num[600];
int str[10][10];
//填了的为0,没填的为1,可以用lowbit找出没填的 
int get(int x,int y){return (x/3*3)+(y/3);}
void change(int x,int y,int k)
{
    row[x]^=1<<k;
    col[y]^=1<<k;
    tub[get(x,y)]^=1<<k;
}
int ans=-1;
int get_val(int x,int y)
{
    if(x==0 || x==8 || y==0 || y==8) return 6;
    if(x==1 || x==7 || y==1 || y==7) return 7;
    if(x==2 || x==6 || y==2 || y==6) return 8;
    if(x==3 || x==5 || y==3 || y==5) return 9;
    return 10;
}
int ttt[10][10];
void dfs(int now,int sum)
{
    if(now==0) 
    {
        if(ans<sum)
        {
        /*  for(int i=0;i<9;i++)
                for(int j=0;j<9;j++)
                    ttt[i][j]=str[i][j];    */
            ans=sum;
        }
        return;
    }
    int tmp=10,x,y;
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
        {
            if(str[i][j]!=0) continue;
            int val=row[i] & col[j] & tub[get(i,j)];//没填的 
            if(!val) return;
            if(cnt[val]<tmp)
            {//找到最容易确定的位置(也就是未填写的最少)来填写 
                tmp=cnt[val];
                x=i,y=j;    
            }
        }
    int val = row[x] & col[y] & tub[get(x,y)];
    for(;val;val-=val&-val)
    {
        int k=num[val&-val];
        str[x][y]=k+1;
        change(x,y,k);
        dfs(now-1,sum+get_val(x,y)*(k+1));
        change(x,y,k);
        str[x][y]=0;
    }
}
int main()
{
    for(int i=0;i<1<<9;i++)
        for(int j=i;j;j-=j&-j)
            cnt[i]++;//计算一个数的二进制位有几个1 
    for(int i=0;i<9;i++)
        num[1<<i]=i;
    for(int i=0;i<9;i++) row[i]=col[i]=tub[i]=(1<<9)-1;
    int tot=0,sum=0;
    for(int i=0;i<9;i++)
        for(int j=0;j<9;j++)
        {   
            scanf("%d",&str[i][j]);
            if(str[i][j]!=0) 
            {
                change(i,j,str[i][j]-1);
                sum+=get_val(i,j)*str[i][j];
            }
            else tot++;
        }

    dfs(tot,sum);/*
        for(int i=0;i<9;i++)
        {
                for(int j=0;j<9;j++)
                    printf("%d ",ttt[i][j]);
                puts("");
            }       */
    printf("%d\n",ans);
    return 0;
}

2、虫食算(传送门

搜索框架:枚举每个字母代表哪个数字。
加几个剪枝:
1、可行性剪枝:如果当前的状态已经不合法就删掉。具体的做法:因为是加法所以进位只可能进一位,分进一位和不进位的情况讨论一下是否合法即可,这一个剪枝可以拿到70pts
2、优化搜索顺序。由于我们是枚举字母代表哪个数字,之前使用的是顺序枚举,我们可以按照字母在原串中出现的先后顺序来枚举,这样会比较快的找到答案。这样就可以拿到剩下的30pts了!

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
char a[50],b[50],c[50];
int n,s[50];
bool used[50];
bool usedzm[50];
int pos[50];
bool check()
{
    for(int i=n-1;i>=0;i--)
    {
        char a1=a[i]-'A',b1=b[i]-'A',c1=c[i]-'A';
        if(s[a1]!=-1 && s[b1]!=-1 && s[c1]!=-1)
        {
            if( (s[a1]+s[b1])%n!=s[c1] && (s[a1]+s[b1]+1)%n!=s[c1] )
                return false;
        }
    }
    return true;
}
bool isok()
{
    int add=0,sum=0;
    for(int i=n-1;i>=0;i--)
    {
        char a1=a[i]-'A',b1=b[i]-'A',c1=c[i]-'A';

        sum=(s[a1]+s[b1]+add)%n;
        add=(s[a1]+s[b1]+add)/n;
        if(sum!=s[c1]) return false;
        sum=0;
    }
    if(add>0) return false;
    return true;
}
bool bk;
void dfs(int k)
{
    if(bk) return;
    if(!check()) return;
    if(k==n)
    {
        if(isok()) 
        {
            for(int i=0;i<n-1;i++) printf("%d ",s[i]);
            printf("%d\n",s[n-1]);
            bk=true;
        }
        return;
    }
    for(int i=n-1;i>=0;i--)
    {
        if(!used[i])
        {
            used[i]=true;
            s[pos[k]]=i;
            dfs(k+1);
            s[pos[k]]=-1;
            used[i]=false;
        }
    }
    return;
}
int main()
{
    scanf("%d",&n);
    scanf("%s%s%s",a,b,c);
    bk=false;
    memset(s,-1,sizeof(s));
    memset(used,false,sizeof(used));
    memset(usedzm,false,sizeof(usedzm));
    int tmp=0;
    for(int i=n-1;i>=0;i--)
    {
        char a1=a[i]-'A',b1=b[i]-'A',c1=c[i]-'A';
        if(!usedzm[a1])
        {
            usedzm[a1]=true;
            pos[tmp++]=a1;
        }
        if(!usedzm[b1])
        {
            usedzm[b1]=true;
            pos[tmp++]=b1;
        }
        if(!usedzm[c1])
        {
            usedzm[c1]=true;
            pos[tmp++]=c1;
        }
    }
    dfs(0);
    return 0;
}

3、The Buses(poj1167)

  首先预处理出所有可能的路线(也就是找等差数列),这里寻找的时候枚举发车时间和间隔,注意因为一路车最少出现两趟,所以发车时间最高为29,而且因为时间表是完整的,所以发车间隔一定大于发车时间。
  可以加入一个可行性剪枝:当前已选线路数+估计所需要的线路(剩余没安排的车÷当前线路所需要的车)>=ans就可以return了。我们在预处理所有线路的时候可以顺便把当前线路所需要的车预处理出来。我们要让估计所需要的线路尽量小,那么就可以让当前线路所需要的车尽量大。所以我们可以按当前线路所需要的车从大到小排序。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int v[100];
int n,cnt,ans;
struct node
{
    int beg,gap,tms;    
}a[20];
bool cmp(node x,node y)
{
    return x.tms>y.tms;
}
bool check(int x,int add)
{
    for(int i=x;i<=59;i+=add)
    {
        if(!v[i]) 
            return false;
    }
    return true;
}
void dfs(int x,int k)
{
    if(n==0)
    {
        ans=min(ans,k);
        return; 
    }
    int t;
    for(t=x;t<=cnt && a[t].tms>n;t++);
    for(int i=t;i<=cnt;i++)
    {
        if(k+(n/a[i].tms)>=ans) return;
        if(check(a[i].beg,a[i].gap))
        {
            for(int j=a[i].beg;j<=59;j+=a[i].gap){v[j]--; n--;}
            dfs(i,k+1); 
            for(int j=a[i].beg;j<=59;j+=a[i].gap){v[j]++; n++;}
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x; scanf("%d",&x);
        v[x]++; 
    }
    cnt=0;
    for(int i=0;i<=29;i++)
        if(v[i])
        {
            for(int j=i+1;j<=59-i;j++)
            {
                if(check(i,j))
                {
                    a[++cnt].beg=i;
                    a[cnt].gap=j;
                    a[cnt].tms=1+(59-i)/j;
                }
            }
        }
    sort(a+1,a+cnt+1,cmp);
    ans=17; dfs(1,0);
    printf("%d\n",ans);
    return 0;
}

4、Missile Defence System(poj3700)
  有趣的问题:给出一组互不相同的整数,求可将其划分为上升(下降)子序列的最少个数。
  
  可以使用一个贪心来剪枝,我们做过的LIS问题里面,是否可以加入一个新的数是取决于最后的最后的一个数和当前数的关系,那么我们可以用两个up[],down[]数组分别记录一下已经处理过的上升(下降)子序列中的最后一个数。以上升序列为例,我们选择处理过的up数组里面小于当前数且最大的那个。为什么?很容易想,因为越小的可以带来更优秀的未来收益,所以我们就把他留给后面,选择可选里面未来收益最低的(也就是最大的那个)可以在保证延长数列的同时使得未来收益最大,最后如果当前没得选的话就自己新开一个序列。
  这样的搜索分叉就只有四叉(延长上升,延长下降,新开上升,新开下降),大大降低了复杂度,而且搜索层数不多,可以迭代一下。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,dep,a[60];
int up[60],down[60];
bool dfs(int k,int u,int d)//当前第k个数,已经处理了u个上升序列,d个下降序列
{
    if(u+d>dep) return false;
    if(k==n+1) return true;
    int pos=0,maxx=0,minn=9999999,tmp;
    for(int i=1;i<=u;i++)
    {
        if(up[i]<a[k] && up[i]>maxx)
        {
            maxx=up[i];
            pos=i;
        }
    }
    if(pos)
    {
        tmp=up[pos];
        up[pos]=a[k];
        if(dfs(k+1,u,d)) return true;
        up[pos]=tmp;
    }
    else
    {
        up[u+1]=a[k];
        if(dfs(k+1,u+1,d)) return true;
    }
    pos=0;
    for(int i=1;i<=d;i++)
    {
        if(down[i]>a[k] && down[i]<minn)
        {
            minn=down[i];
            pos=i;
        }
    }
    if(pos)
    {
        tmp=down[pos];
        down[pos]=a[k];
        if(dfs(k+1,u,d)) return true;
        down[pos]=tmp;
    }
    else
    {
        down[d+1]=a[k];
        if(dfs(k+1,u,d+1)) return true;
    }
    return false;
}
int main()
{
    while(scanf("%d",&n)&&n)
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(dep=1;;dep++)
        {
            if(dfs(1,0,0))
            {
                printf("%d\n",dep);
                break;
            }
        }
    }
    return 0;
}

5、武士风度的牛(codevs1411)
套路广搜,太水。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int dx[]={1,1,-1,-1,2,2,-2,-2};
const int dy[]={2,-2,2,-2,1,-1,1,-1};
struct node
{
    int x,y;
};
queue<node> q;
char map[160][160];
int d[160][160]; bool v[160][160];
int main()
{
    int n,m;scanf("%d%d",&m,&n);
    memset(d,63,sizeof(d));
    memset(v,false,sizeof(v));
    for(int i=1;i<=n;i++)
    {
        scanf("%s",map[i]+1);
        for(int j=1;j<=m;j++)
        {
            if(map[i][j]=='K')
            {
                node st;
                st.x=i,st.y=j;  
                d[i][j]=0,v[i][j]=true;
                q.push(st);
            }
        }
    }
    while(!q.empty())
    {
        node tno=q.front(); q.pop();
        for(int k=0;k<8;k++)
        {
            int tx=tno.x+dx[k],ty=tno.y+dy[k];
            if(tx<1 || tx>n || ty<1 || ty>m || map[tx][ty]=='*') continue;
            if(d[tx][ty]>d[tno.x][tno.y]+1)
            {
                d[tx][ty]=d[tno.x][tno.y]+1;
                if(!v[tx][ty])
                {
                    v[tx][ty]=true;
                    if(map[tx][ty]=='H')
                    {
                        printf("%d\n",d[tx][ty]);
                        return 0;
                    }
                    node nw; nw.x=tx,nw.y=ty;
                    q.push(nw);
                }
            }
        }
    }
    return 0;
}

7、乳草的入侵(TYVJ1030)
套路广搜,太水。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
struct node{int x,y;};
int n,m,stx,sty;
const int dx[]={0,0,1,-1,1,-1,1,-1};
const int dy[]={1,-1,0,0,1,-1,-1,1};
char map[110][110];
int v[110][110];
queue<node> q;
int main()
{
    scanf("%d%d%d%d",&m,&n,&stx,&sty);
    swap(stx,sty); stx=n-stx+1;
    for(int i=1;i<=n;i++)
        scanf("%s",map[i]+1);
    map[stx][sty]='M';
    node tno; tno.x=stx,tno.y=sty;
    q.push(tno);
    memset(v,-1,sizeof(v)); v[stx][sty]=0;
    int ans=0;
    while(!q.empty())
    {
        tno=q.front(); q.pop();
        for(int k=0;k<8;k++)
        {
            int tx=tno.x+dx[k],ty=tno.y+dy[k];
            if(tx<1 || tx>n || ty<1 || ty>m || map[tx][ty]=='*' || map[tx][ty]=='M') continue;
            if(v[tx][ty]==-1)
            {
                v[tx][ty]=v[tno.x][tno.y]+1;
                map[tx][ty]='M';
                ans=max(ans,v[tx][ty]);
                node nxt; nxt.x=tx,nxt.y=ty;
                q.push(nxt);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

9、子串变换
  首先题目条件有“超过十层搜索不到答案就退出”这个条件,所以一开始想的是迭代加深鏼鏼鏼,后来发现替换条件最多有20个,那么搜索树有20层,指数级复杂度迭代鏼不动啊。
  所以我们另辟他径,发现起始状态和终点状态都给出了,那么可以考虑双向搜索,这样指数级复杂度折半还是可以接受的。基本思路和注意事项看下方代码吧,会好理解很多,这里不赘述了。
  对了,对于字串的替换这一方面直接暴力的话会难做且易错。所以string大法好啊!!不信你们看:
 

a.replace(pos,len,b) 将串中从pos开始,长度为len的位置替换为串b。
a.find(b,pos) 从a串中第pos位开始找,返回b串第一次出现的位置的起点

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
string A,B,list[2][1000010];
string p[2][10]; int cnt;
map<string,int> M[2];
int dep=0;
int head1,tail1,head2,tail2;
bool BFS(int w)
{
    string s_next,s_prev;
    int head,tail;
    if(w==0) head=head1,tail=tail1;
    else head=head2,tail=tail2;
    for(int t=head;t<=tail;t++)//注意搜索只能搜一层,别再往下搜了 
    {
        for(int i=1;i<=cnt;i++)
        {
            int pos=0;
            while(pos!=-1)
            {
                s_prev=list[w][t];
//              cout<<w<<' '<<s_prev<<endl;
                pos=s_prev.find(p[w][i],pos);
                if(pos>=0)
                {
                    s_next=s_prev.replace(pos,p[w][i].length(),p[w^1][i]);
//                  cout<<w<<' '<<s_next<<endl;
                    if(M[w][s_next]==0)//map去重 
                    {
                        if(M[w^1][s_next]!=0)//如果搜到别人已经搜到的状态,说明搜索完成碰面了。 
                            return true;
                        M[w][s_next]=1;
                        if(!w) list[0][++tail1]=s_next;
                        else list[1][++tail2]=s_next;
                    }
                }
                if(pos!=-1) pos+=p[w^1][i].length();
            }
        }
    }
    if(!w) head1=tail+1;
    else head2=tail+1;
    return false;
}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    cin>>A>>B;
    cnt=1;
    while(cin>>p[0][cnt]>>p[1][cnt]) 
        cnt++;
    cnt--;//debug
    M[0][A]=1; M[1][B]=1;
    head1=1,tail1=1;head2=1,tail2=1;
    list[0][1]=A,list[1][1]=B;
    for(dep=1;;dep++)
    {
        if(dep>5) {puts("NO ANSWER!");return 0;}
        if(BFS(0)){printf("%d\n",dep*2-1);return 0;}
        if(BFS(1)){printf("%d\n",dep*2);return 0;}//注意这里dep不能减一 
        /*
        双向搜索就是你走一步我走一步,这里假如0走到了,因为0先走已经走到了,后面1不需要再走了所以要减一
        假如1走到了,那也要前面0先走出那一步才可以撞上,所以不需要减一。 
        */
    }
    return 0;
}

10、Mayan游戏
  这题实际上是一个大模拟……,主要部分是一个drop()函数和一个cancel()函数,cancel函数执行在这个图里面的三个或多个相同方块删除的工作,drop则是执行方块受重力下降的工作。cancel的实现相对比较复杂,需要分别判横排和竖排(注意只需要从左往右判断就可以,因为从右往左重复了),在判断横排的同时判断竖排有没有可以拓展的空间即可。
  通过搜索枚举移动哪个木块作为框架即可。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
int n;
struct ANS
{
    int x,y,opt;
}ans[10];
int a[10][10];
bool isok()
{
    for(int i=0;i<5;i++)
        for(int j=0;j<7;j++)
            if(a[i][j]) return false;
    return true;
}
void drop()
{
    int tmp[10][10];
    memset(tmp,-1,sizeof tmp);
    for(int i=0;i<5;i++)
    {
        int h=0;
        for(int j=0;j<7;j++)
            if(a[i][j])
                tmp[i][h++] = j;
    }
    for(int i=0;i<5;i++)
        for(int j=0;j<7;j++)
        {
            if(tmp[i][j]!=-1) a[i][j]=a[i][tmp[i][j]];
            else a[i][j]=0; 
        }

    return;
}
bool cancel()
{
    bool flag=false;
    for(int i=0;i<3;i++)
        for(int j=0;j<7;j++)
        if(a[i][j])
        {
            int x_max;
            for(x_max=i;x_max+1<5 && a[x_max+1][j]==a[i][j];x_max++);
            if(x_max-i>=2)
            {
                int tx;
                for(tx=i;tx<=x_max;tx++)
                {
                    int l=j,r=j;
                    while(r+1<7 && a[tx][r+1]==a[i][j]) r++;
                    while(l-1>=0 && a[tx][l-1]==a[i][j]) l--;
                    if(r-l>=2)
                    {
                        int ty;
                        for(ty=l;ty<=r;ty++)
                            a[tx][ty]=0;
                    }
                }
                for(tx=i;tx<=x_max;tx++)
                    a[tx][j]=0;
                flag=1;
            }
        }
    for(int i=0;i<5;i++)
        for(int j=0;j<5;j++)
        if(a[i][j])
        {
            int y_max;
            for(y_max=j;y_max+1<7 && a[i][y_max+1]==a[i][j];y_max++);
            if(y_max-j>=2)
            {
                int ty;
                for(ty=j;ty<=y_max;ty++)
                {
                    int l=i,r=i;
                    while(l-1>=0 && a[l-1][ty]==a[i][j]) l--;
                    while(r+1<7  && a[r+1][ty]==a[i][j]) r++;
                    if(r-l>=2)
                    {
                        int tx;
                        for(tx=l;tx<=r;tx++)
                            a[tx][ty]=0;
                    }
                }
                for(ty=j;ty<=y_max;ty++)
                    a[i][ty]=0;
                flag=1;
            }
        }
    if(flag) return true;
    else return false;
}
void dfs(int k)
{
    if(k>n)
    {
        if(isok())
        {
            for(int i=1;i<=n;i++)
            {
                if(ans[i].opt) printf("%d %d -1\n",ans[i].x+1,ans[i].y);
                else printf("%d %d 1\n",ans[i].x,ans[i].y);
            }
            exit(0);
        }
        return;
    }

    int sum[20];
    memset(sum,0,sizeof sum);
    for(int i=0;i<5;i++)
        for(int j=0;j<7;j++)
            sum[a[i][j]]++;
    for(int i=1;i<=10;i++)
        if(sum[i]!=0&&sum[i]<3) 
            return;
    for(int i=0;i<4;i++)
        for(int j=0;j<7;j++)
        if(a[i][j]!=a[i+1][j])
        {
            ans[k].x=i;
            ans[k].y=j;
            ans[k].opt=a[i][j]?0:1;
            int tmp[10][10];
            memcpy(tmp,a,sizeof(tmp));
            swap(a[i][j],a[i+1][j]);
            drop();
            while(cancel()) drop();

            dfs(k+1);

            ans[k].x=0;
            ans[k].y=0;
            ans[k].opt=0;
            memcpy(a,tmp,sizeof(a));
        }
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<5;i++)
    {
        for(int j=0;;j++)
        {
            scanf("%d",&a[i][j]);
            if(a[i][j]==0) break;
        }
    }
    dfs(1);
    printf("-1\n");
    return 0;
}

11、Weather Forecast(poj2044)
  如果直接搜索九个方向,365天,同时还要搜索十六个格子的下雨状态,时间和空间都是完全过不去的。那么有什么方法可以降低搜索的时空复杂度呢?九个方向,365天都是没法避免的搜索复杂度,但是十六个格子的下雨状态可以不用都记录,我们需要满足一个地方不能连续七天不下雨,所以我们可以选择一些下雨相对较少的位置来记录他们的状态,如果他们都连续七天没下雨那么肯定当前状态是不行的。那么下雨相对较少的位置在哪里呢?就是四个角的位置,很容易想明白吧为什么吧,因为云的统共九种位置中,每一种都可以覆盖到中间的位置,然而只有特殊的四种可以覆盖到四个角,所以我们只需要记录四个角的连续不下雨天数做出判断即可。
  还有,在记录地区下雨需求方面,我们可以用一个位运算来进行,把当前的下雨状态用16位二进制表示出来,有下雨的地区就设为1,和当天的需求(也是一个十六位二进制,1代表这一个地区今天不要下雨)进行与运算,如果结果大于0,也就是有两个1冲突了(也就是在不该下雨的位置下雨了),那这个状态显然不行。
  判断重复方面可以开一个vis六维数组,记录:
  vis[第几天][云的位置(云的左上角方块编号)][左上角连续不下雨天数][右上][左下][右下]
  
 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct status
{
    int a,b,c,d;//四个角状态的连续不下雨天数 
    status(){a=0,b=0,c=0,d=0;}  
};
struct node
{
    int x,y;//只记录左上角状态  
};
const int dx[]={0,1,2,-1,-2,0,0,0,0};
const int dy[]={0,0,0,0,0,1,2,-1,-2};
bool vis[366][12][7][7][7][7];
int rain[366];
int n;
bool check(int k,node now,status s)
{
    if(s.a>6 || s.b>6 || s.c>6 || s.d>6)
        return false;

    int tmp=0;
    tmp|=( (1<<(4*now.x+now.y)) | (1<<(4*now.x+now.y+1)) );
    tmp|=( (1<<(4*(now.x+1)+now.y)) | (1<<(4*(now.x+1)+(now.y+1))) );
    if((tmp&rain[k])!=0)
        return false;

    if(vis[k][(4*now.x+now.y)][s.a][s.b][s.c][s.d]) 
        return false;
    vis[k][(4*now.x+now.y)][s.a][s.b][s.c][s.d]=true;
    return true;

}
bool dfs(int k,node now,status s)
{
    if(k==n) return true;

    status ns=s;
    ns.a+=1; ns.b+=1; ns.c+=1; ns.d+=1;
    if(now.x==0 && now.y==0) ns.a=0;
    if(now.x==0 && now.y==2) ns.b=0;
    if(now.x==2 && now.y==0) ns.c=0;
    if(now.x==2 && now.y==2) ns.d=0;

    if(!check(k,now,ns))
        return false;

    for(int i=0;i<9;i++)
    {
        int tx=now.x+dx[i],ty=now.y+dy[i];
        if(tx<0 || tx>2 || ty<0 || ty>2) continue;
        node nw; nw.x=tx,nw.y=ty;
        if(dfs(k+1,nw,ns))
            return true;    
    }
    return false;
}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    while(scanf("%d",&n)&&n)
    {
        memset(rain,0,sizeof(rain));
        memset(vis,false,sizeof(vis));
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<16;j++)
            {
                int x;scanf("%d",&x);
                rain[i]<<=1;
                rain[i]|=x;
            }
        }
        status st; node stt;
        stt.x=1; stt.y=1;
        if(dfs(0,stt,st))
            puts("1");
        else
            puts("0");
    }
}

12、Flood-it!(poj4007)
  搜索框架:枚举当前填什么颜色,然后其实我们不需要实际改变原来的颜色,我们可以把他们看成若干块同化的过程,我们设一个v数组,v[i][j]=1表示(i,j)属于左上角的连通块,v[i][j]=2表示(i,j)属于联通块即将同化的那一块,v[i][j]=0表示右下角的偏远地带,当前我们不理他们。然后每次枚举同化的颜色实际上是v数组改变的过程,不需要实际改变原来颜色(因为原来的颜色是我们搜索判断的标准,在代码中可以体现)。
  剪枝:明显可以IDA*优化,我在A*与IDA*中提到:我们设计估价函数,可以从“达成一个目标最少需要多少代价”来思考。那么我们从这方面思考:当前矩阵中除了左上角的连通块之外,还有多少种颜色,那么还需要的步数不小于颜色数(最少也需要颜色数那么多步来同化),以这个颜色数作为当前状态估价函数即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int a[10][10];
int vis[10][10],v[10][10];
int n;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
void spread(int x,int y,int c)
{
    v[x][y]=1;
    for(int i=0;i<4;i++)
    {
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<1 || tx>n || ty<1 || ty>n || v[tx][ty]==1) continue;  
        v[tx][ty]=2;
        if(a[tx][ty]==c) spread(tx,ty,c);
    }
}
int As()//当前矩阵中除了左上角的连通块之外,还有多少种颜色,那么还需要的步数不小于颜色数 
{
    int cnt=0; bool tmp[10];
    memset(tmp,false,sizeof(tmp));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(v[i][j]!=1 && !tmp[a[i][j]])
            {
                cnt++;
                tmp[a[i][j]]=true;
            }   
        }
    return cnt;
}
bool find(int c)
{
    int cnt=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(a[i][j]==c && v[i][j]==2)
            {
                cnt++;
                spread(i,j,c);  
            }
        }
    return cnt;
}
int dep; bool flag;
void dfs(int k)
{
    if(flag) return ;
    int s=As();
    if(s+k>dep) return;
    if(s==0){flag=true; return;}
    int tmp[10][10];
    for(int i=0;i<=5;i++)
    {
        memcpy(tmp,v,sizeof(tmp));
        if(find(i)) dfs(k+1);
        memcpy(v,tmp,sizeof(v));
    }
    return;
}
int main()
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);
    while(scanf("%d",&n)&&n)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&a[i][j]);
        memset(v,0,sizeof(v));
        flag=false;
        spread(1,1,a[1][1]);
        for(dep=0;;dep++)
        {
            dfs(0);
            if(flag) break; 
        }
        printf("%d\n",dep);
    }
    return 0;
}

13、骑士精神(bzoj1085)
  搜索框架:以空格位置为核心枚举8个方向让空格跳(这是类似问题的一般搜索做法)
  剪枝:经典的IDA*,和八数码问题类似,求目标矩阵与当前矩阵的差值作为移动到最终状态的最小代价,这题相较与八数码比较简单,只需要求出当前矩阵和目标矩阵的不同数即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int ans[6][6]=
{
    {1,1,1,1,1},
    {0,1,1,1,1},
    {0,0,2,1,1},
    {0,0,0,0,1},
    {0,0,0,0,0},    
};
const int dx[]={1,-1,1,-1,2,-2,2,-2};
const int dy[]={2,2,-2,-2,1,1,-1,-1};
int a[6][6];
bool isok()
{
    for(int i=0;i<5;i++)
        for(int j=0;j<5;j++)
            if(a[i][j]!=ans[i][j])
                return false;
    return true;
}
int dep; 
int As()
{
    int cnt=0;
    for(int i=0;i<5;i++)
        for(int j=0;j<5;j++)
            if(a[i][j]!=ans[i][j])
            {
                cnt++;
            }
    return cnt;
}
bool flag;
void dfs(int x,int y,int k)
{
    if(flag) return;
    if(k==dep)
    {
        if(isok()) flag=true;
        return;
    }
    for(int i=0;i<8;i++)
    {
        int tx=x+dx[i],ty=y+dy[i];
        if(tx<0 || tx>4 || ty<0 || ty>4 ) continue;
        swap(a[x][y],a[tx][ty]);
        if(As()+k<=dep) dfs(tx,ty,k+1);
        swap(a[x][y],a[tx][ty]);    
    }
    return;
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        char st[6][6]; int stx,sty;
        for(int i=0;i<5;i++)
        {
            scanf("%s",st[i]);
            for(int j=0;j<5;j++)
            {
                if(st[i][j]=='0')
                    a[i][j]=0;
                else if(st[i][j]=='1')
                    a[i][j]=1;
                else 
                {
                    a[i][j]=2;
                    stx=i,sty=j;
                }
            }
        }
        flag=false;
        for(dep=1;dep<=15;dep++)
        {
            dfs(stx,sty,0);
            if(flag){printf("%d\n",dep); break;}
        }
        if(!flag) puts("-1");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值