kuangbin专题一简单搜索(上)


注意此处提供了链接

kuangbin专题总
kuangbin专题1

1. 棋盘问题

问题描述

  • 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。
  • 要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放$k$个棋子的所有可行的摆放方案数目$C$。
输入格式

输入含有多组测试数据.
每组数据的第一行是两个正整数$n,k$,用一个空格隔开,表示了将在一个$n∗n$的矩阵内描述棋盘,以及摆放棋子的数目。当为-1 -1时表示输入结束。
随后的$n$行描述了棋盘的形状:每行有$n$个字符,其中#表示棋盘区域,.表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出格式

对于每一组数据,给出一行输出,输出摆放的方案数目$C$(数据保证 $C<2^{31}$)。

数据范围

$n \leqslant 8, k \leqslant n$

输入样例

$2$ $1$
#.
.#
$4$ $4$
…#
..#.
.#..
#…
$-1$ $-1$

输出样例

$2$
$1$

思路

经典dfs问题.

  • 方法一: 枚举每一个元素,时间复杂度为$O(2^{n^2})$.
  • 方法二: 枚举每一行(或每一列),时间复杂度为$O(n!)$.

代码

  • 方法一 :枚举每一个元素
#include <iostream>
#include <cstring>
#include <algorithm>

#define MAXN 10

using namespace std;

int n,k,res;
char g[MAXN][MAXN];
int row[MAXN],col[MAXN];

void dfs(int x,int y,int cnt)
{
    if(y==n+1)
        y=1,x++;
    if(x==n+1)
    {
        if(cnt==k)
            res++;
        return ;
    }
    if(g[x][y]=='#'&&row[x]==0&&col[y]==0)
    {
        g[x][y]='.';
        row[x]=col[y]=1;
        dfs(x,y+1,cnt+1);
        g[x][y]='#';//恢复现场
        row[x]=col[y]=0;
    }
    dfs(x,y+1,cnt);//不选(x,y)的元素
}

int main()
{
    while(scanf("%d %d",&n,&k)!=EOF)
    {
        if(n==-1&&k==-1)
            break;
        for(int i=1;i<=n;i++)
            scanf("%s",g[i]+1);
        res=0;
        //memset(col,0,sizeof(col));
        //memset(row,0,sizeof(row));
        dfs(1,1,0);
        printf("%d\n",res);
    }
    return 0;
}
  • 方法二: 枚举每一行(或每一列).
#include <iostream>
#include <cstring>
#include <algorithm>

#define MAXN 10

using namespace std;

int n,k,res;
char g[MAXN][MAXN];
int col[MAXN];//标记该列是否使用

void dfs(int root,int cnt)
{
    if(root==n+1)//所有行都枚举完
    {
        if(cnt==k)//且使用的次数刚好为k
            res++;
        return ;
    }
    for(int i=1;i<=n;i++)
    {
        if(g[root][i]=='#'&&col[i]==0)
        {//该点为棋盘元素,且没被访问过
            g[root][i]='.';
            col[i]=1;
            dfs(root+1,cnt+1);//选当前行,枚举下一行
            col[i]=0;//恢复现场
            g[root][i]='#';
        }
    }
    dfs(root+1,cnt);//当前行不选
}

int main()
{
    while(scanf("%d %d",&n,&k)!=EOF)
    {
        if(n==-1&&k==-1)
            break;
        res=0;
        //memset(col,0,sizeof(col));
        //此处col可以不用清0,由于dfs会恢复现场使得col数组最终为0
        for(int i=1;i<=n;i++)
            scanf("%s",g[i]+1);
        dfs(1,0);
        printf("%d\n",res);
    }
    return 0;
}

2. 地牢大师

问题描述

你现在被困在一个三维地牢中,需要找到最快脱离的出路!
地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。
向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。
你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。
请问,你有可能逃脱吗?
如果可以,需要多长时间?

输入格式

输入包含多组测试数据。
每组数据第一行包含三个整数$L,R,C$分别表示地牢层数,以及每一层地牢的行数和列数。
接下来是$L$个$R$行$C$列的字符矩阵,用来表示每一层地牢的具体状况。
每个字符用来描述一个地牢单元的具体状况。
其中, 充满岩石障碍的单元格用#表示,不含障碍的空单元格用.表示,你的起始位置用S表示,终点用E表示。
每一个字符矩阵后面都会包含一个空行。
当输入一行为0 0 0时,表示输入终止。

输出格式

每组数据输出一个结果,每个结果占一行。
如果能够逃脱地牢,则输出Escaped in X minute(s).,其中$X$为逃脱所需最短时间。
如果不能逃脱地牢,则输出Trapped!

数据范围

$1\leqslant L,R,C \leqslant 100$

输入样例

$3$ $4$ $5$
S….
.###.
.##..
###.#

#####
#####
##.##
##…

#####
#####
#.###
####E

$1$ $3$ $3$
S##
#E#
###

$0$ $0$ $0$

输出样例

Escaped in 11 minute(s).
Trapped!

思路

三维bfs,注意移动方向为上下前后左右$6$个方向.

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

#define MAXN 110

using namespace std;

int h,n,m;//高(z),长(x),宽(y)
int dir[6][3]={
    {1  ,0  ,0  },
    {-1 ,0  ,0  },
    {0  ,1  ,0  },
    {0  ,-1 ,0  },
    {0  ,0  ,1  },
    {0  ,0  ,-1 }
};
struct node{
    int x;
    int y;
    int z;
    int step;
};
node start_,end_;
char g[MAXN][MAXN][MAXN];
int vis[MAXN][MAXN][MAXN];

int bfs()
{
    queue<node> q;    
    q.push(start_);
    vis[start_.z][start_.x][start_.y]=1;
    while(q.size()>0)
    {
        node temp=q.front();
        q.pop();
        if(temp.x==end_.x&&temp.y==end_.y&&temp.z==end_.z)
            return temp.step;
        for(int i=0;i<=5;i++)
        {
            int dx=temp.x+dir[i][1],dy=temp.y+dir[i][2],dz=temp.z+dir[i][0];
            if(dx>=1&&dx<=n&&dy>=1&&dy<=m&&dz>=1&&dz<=h&&vis[dz][dx][dy]==0&&g[dz][dx][dy]!='#')
            {
                vis[dz][dx][dy]=1;
                q.push({dx,dy,dz,temp.step+1});
            }
        }
    }
    return -1;
}

int main()
{
    while(scanf("%d %d %d",&h,&n,&m)!=EOF)
    {
        if(h==0&&n==0&&m==0)
            break;
        memset(vis,0,sizeof(vis));
        for(int k=1;k<=h;k++)
            for(int i=1;i<=n;i++)
            {
                scanf("%s",g[k][i]+1);
                for(int j=1;j<=m;j++)
                {
                    if(g[k][i][j]=='S')
                        start_={i,j,k};
                    else if(g[k][i][j]=='E')
                        end_={i,j,k};
                }
            }
        int res=bfs();
        if(res==-1)
            printf("Trapped!\n");
        else printf("Escaped in %d minute(s).\n",res);
    }
    return 0;
}

3. 抓住那头牛

问题描述

农夫知道一头牛的位置,想要抓住它。
农夫和牛都位于数轴上,农夫起始位于点$N$,牛位于点$K$。
农夫有两种移动方式:

  • 从 $X$ 移动到 $X−1$ 或 $X+1$,每次移动花费一分钟;
  • 从 $X$ 移动到 $2∗X$,每次移动花费一分钟.
    假设牛没有意识到农夫的行动,站在原地不动。
    农夫最少要花多少时间才能抓住牛?
输入格式

共一行,包含两个整数$N$和$K$。

输出格式

输出一个整数,表示抓到牛所花费的最少时间。

数据范围

$0 \leqslant N,K \leqslant 10^5$

输入样例

$5$ $17$

输出样例

$4$

思路

经典bfs,一维搜索,此处可不用判断农夫与牛的位置关系.

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

#define MAXN 100010

using namespace std;

struct node{
    int pos;
    int step;
};
node start_,end_;
int vis[MAXN];
int dir[3]={-1,1,2};

int bfs()
{
    queue<node> q;
    q.push(start_);
    vis[start_.pos]=1;
    while(q.size()>0)
    {
        node temp=q.front();
        q.pop();
        if(temp.pos==end_.pos)
            return temp.step;
        for(int i=0;i<=2;i++)
        {
            int dx;
            if(dir[i]==2)
                dx=temp.pos*2;
            else dx=temp.pos+dir[i];
            if(dx>=0&&dx<=100000&&vis[dx]==0)
            {
                vis[dx]=1;
                q.push({dx,temp.step+1});
            }
        }
    }
    return -1;
}

int main()
{
    scanf("%d %d",&start_.pos,&end_.pos);
    printf("%d\n",bfs());
    return 0;
}

4. 翻转

问题描述

给定一个 $M×N$ 的 $01$ 矩阵。
你需要选择其中一些元素,并对选择的元素进行翻转操作。
翻转操作是指将所选元素以及与其上下左右相邻的元素(如果有)进行翻转($0$ 变 $1$,$1$ 变 $0$)。
我们希望最终矩阵变为一个全 $0$ 矩阵,并且选择进行翻转操作的元素数量尽可能少。
输出最佳翻转方案。

输入格式

第一行包含整数 $M,N$。
接下来 $M$ 行,每行包含 $N$ 个整数,每个整数为 $0$ 或 $1$。

输出格式

共 $M$ 行,每行包含 $N$ 个整数,其中第 $i$ 行第 $j$ 列的整数,表示第 $i$ 行第 $j$ 列元素的翻转次数。
如果翻转操作次数最少的操作方案不唯一,则输出字典序最小的方案。
如果不存在合理方案,则输出 IMPOSSIBLE

数据范围

$1 \leqslant M,N \leqslant 15$

输入样例

$4$ $4$
$1$ $0$ $0$ $1$
$0$ $1$ $1$ $0$
$0$ $1$ $1$ $0$
$1$ $0$ $0$ $1$

输出样例

$0$ $0$ $0$ $0$
$1$ $0$ $0$ $1$
$1$ $0$ $0$ $1$
$0$ $0$ $0$ $0$

思路

思想同费解的开关 费解的开关代码
先枚举第一行的所有状态(用二进制表示),然后逐层将当前位置$(x,y)$为$1$的将$(x+1,y)$进行翻转(避免后效性),最后判断最后一行是否均为0,满足条件更新最小翻转次数及翻转情况.

代码

#include <iostream>
#include <cstring>
#include <algorithm>

#define MAXN 20

using namespace std;

const int inf=0x3f3f3f3f;

int m,n,cnt,res=inf;
int a[MAXN][MAXN],vis[MAXN][MAXN];
int temp[MAXN][MAXN];//暂存原数组并对它进行修改
int dir[5][2]={{0,0},{-1,0},{0,1},{1,0},{0,-1}};
int b[MAXN][MAXN];

void dfs(int x,int y)
{
    for(int i=0;i<=4;i++)
    {
        int dx=x+dir[i][0],dy=y+dir[i][1];
        if(dx>=0&&dx<=m-1&&dy>=0&&dy<=n-1)
            temp[dx][dy]^=1;
    }
    vis[x][y]=1;
}

int main()
{
    scanf("%d %d",&m,&n);
    for(int i=0;i<=m-1;i++)
        for(int j=0;j<=n-1;j++)
            scanf("%d",&a[i][j]);
    for(int state=0;state<(1<<n);state++)
    {
        cnt=0;
        memset(vis,0,sizeof(vis));
        memcpy(temp,a,sizeof(temp));
        for(int i=0;i<=n-1;i++)//将该状态进行翻转
            if((state>>i) &1)
                dfs(0,i),cnt++;
        for(int i=0;i<=m-2;i++)
            for(int j=0;j<=n-1;j++)
                if(temp[i][j]==1)
                    dfs(i+1,j),cnt++;
        int flag=1;
        for(int i=0;i<=n-1;i++)
            if(temp[m-1][i]==1)
            {
                flag=0;
                break;
            }
        if(cnt<res&&flag==1)
        {
            res=cnt;
            memcpy(b,vis,sizeof(b));
        }
    }
    if(res==inf)
        printf("IMPOSSIBLE\n");
    else
    {
        for(int i=0;i<=m-1;i++)
        {
            for(int j=0;j<=n-1;j++)
                printf("%d ",b[i][j]);
            printf("\n");
        }
    }
    return 0;
}

5. 找倍数

问题描述

给定一个正整数$n$,请你找到一个它的非零倍数$m$。
要求$m$中只包含数字$0$或$1$,并且总位数不超过 $100$位。

输入格式

输入包含多组测试数据。
每组数据占一行,包含一个正整数$n$。
当输入$n=0$时,表示输入结束。

输出格式

每组数据输出一行$m$。
如果方案不唯一,则输出任意合理方案均可。

数据范围

$1 \leqslant n \leqslant 200$

输入样例

$2$
$6$
$19$
$0$

输出样例

$10$
$100100100100100100$
$111111111111111111$

思路

bfs的思想,初始让$1$放入队列,每次取出队头$x$,判断是否满足$x%n==0$,不满足将$10\times x$和$10 \times x+1$放入队列中.

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long ll;

int n;

ll bfs()
{
    queue<ll> q;
    q.push(1);
    while(q.size()>0)
    {
        ll temp=q.front();
        q.pop();
        if(temp%n==0)
            return temp;
        q.push(temp*10);
        q.push(temp*10+1);
    }
    return -1;
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0)
            break;
        printf("%lld\n",bfs());
    }
    return 0;
}

6. 质数路径

问题描述

给定两个四位质数$A$和$B$,你需要通过最少的操作次数将$A$变为$B$。
每次操作只能改变当前数的其中一位数字,并且每次操作过后,当前数必须仍然是一个质数。
例如,将$1033$变为$8179$,最少需要进行$6$次操作,具体操作为:
1033 -> 1733 -> 3733 -> 3739 -> 3779 -> 8779 -> 8179
请计算并输出所需要的最少操作次数。

输入格式

第一行包含整数$T$,表示共有$T$组测试数据。
每组数据占一行,包含两个四位质数$A$和$B$。

输出格式

每组数据输出一行答案,表示所需最少操作次数。
如果无法做到,则输出 Impossible
经实际测试,不存在无解情况,特此声明。

数据范围

$1 \leqslant T \leqslant 100$.
$ 1000 \leqslant A,B \leqslant 9999$,保证$A$和$B$都是质数.

输入样例

$3$
$1033$ $8179$
$1373$ $8017$
$1033$ $1033$

输出样例

$6$
$7$
$0$

思路

每一个数最多$9\times 4$的变化,利用bfs找出最短路径即可.

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>

#define MAXN 10010

using namespace std;

int t,start_,end_;
int primes[MAXN],is_prime[MAXN],cnt;//判断是否为素数
int vis[MAXN];//标记搜索是否使用过

struct node{
    int num;
    int step;
};

void get_primes(int n)
{//欧拉筛
    for(int i=2;i<=n;i++)
    {
        if(vis[i]==0)
            primes[cnt++]=i;
        for(int j=0;primes[j]<=n/i;j++)
        {
            is_prime[primes[j]*i]=1;
            if(i%primes[j]==0)
                break;
        }
    }
}

int bfs()
{
    queue<node> q;
    memset(vis,0,sizeof(vis));
    q.push({start_,0});
    vis[start_]=1;
    while(q.size()>0)
    {
        node temp=q.front();
        q.pop();

        if(temp.num==end_)
            return temp.step;
        int x[4]={temp.num/1000,temp.num/100%10,temp.num/10%10,temp.num%10};
        //分别存储千位、百位、十位、个位
        for(int i=0;i<=3;i++)
        {
            int tempnum=x[i];//暂存该位
            for(int j=0;j<=9;j++)
            {
                if(tempnum!=j)
                {
                    x[i]=j;//修改该位
                    int sum=1000*x[0]+100*x[1]+10*x[2]+x[3];
                    if(is_prime[sum]==0&&vis[sum]==0&&sum>=1000&&sum<=9999)
                    {
                        vis[sum]=1;
                        q.push({sum,temp.step+1});
                    }
                }
            }
            x[i]=tempnum;//复原该位
        }
    }
    return -1;
}

int main()
{
    get_primes(10000);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&start_,&end_);
        printf("%d\n",bfs());
    }
    return 0;
}

7. 洗牌

问题描述

给定两叠纸牌$S_1$和$S_2$,每叠恰好有$C$张牌。
每张牌的尺寸大小都完全相同,但是颜色可能不同。
下面介绍洗牌规则。
不妨设$S_1$中纸牌从上到下编号依次为 $a_1,a_2,…,a_C$,$S_2$ 中纸牌从上到下编号依次为$b_1,b_2,…,b_C$。
洗牌就是将这两叠牌交错堆叠在一起,形成一个拥有 $2C$张牌的新牌堆$S_{12}$。
新牌堆中的牌由上至下依次为 $a_1,b_1,a_2,b_2,…,a_C,b_C$。
可参考下图:

然后,将牌堆从中间一分为二,下半部分是新的 $S_1$,上半部分是新的 $S_2$。
这样就可以继续进行洗牌操作获得新的$S_{12}$了。
给定$S_1$和$S_2$,请问至少需要进行多少轮洗牌操作方可获得指定牌堆$S_{12}$。

输入格式

第一行包含一个整数$T$,表示共有$T$组测试数据。
每组数据第一行包含一个整数$C$。
第二行包含一个长度为$C$的由大写字母构成的字符串,其中第$i$个字母表示初始$S_1$中由底向上第$i$张牌的颜色。
第三行包含一个长度为$C$的由大写字母构成的字符串,其中第$i$个字母表示初始$S_2$中由底向上第$i$张牌的颜色。
第四行包含一个长度为$2C$的由大写字母构成的字符串,其中第$i$个字母表示目标$S_{12}$中由底向上第$i$张牌的颜色。

输出格式

共$T$行,每行输出一组数据的结果,首先输出组别编号$i$(从$1$开始),然后输出所需要的最少洗牌次数。如果无法通过洗牌获得目标牌堆,则输出$−1$。

数据范围

$1 \leqslant T \leqslant 1000$,
$1 \leqslant C \leqslant 100$.
卡牌最多有$8$种颜色,用大写字母$A∼H$表示,所以输入字符串中不会出现其他大写字母。

输入样例

$2$
$4$
$AHAH$
$HAHA$
$HHAAAAHH$
$3$
$CDE$
$CDE$
$EEDDCC$

输出样例

$1$ $2$
$2$ $-1$

思路

此题为模拟+bfs(可不用bfs),最多枚举$2C$次模拟操作,就可以知道能否得到该字符串.

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <map>

using namespace std;

int t,n,cas=1;
string s1,s2,res;

void bfs()
{
    string temp;
    for(int i=0;i<n;i++)
        temp+=s2[i],temp+=s1[i];
    map<string,int> mp;
    queue<string> q;
    q.push(temp);
    mp[temp]=1;
    while(q.size()>0)
    {
        string t=q.front();
        q.pop();
        if(t==res)
        {
            printf("%d %d\n",cas++,mp[t]);
            return ;
        }
        string now;
        for(int i=0;i<n;i++)
            now+=t[i+n],now+=t[i];
        if(mp[now]==0)
        {
            mp[now]=mp[t]+1;
            q.push(now);
        }
    }
    printf("%d -1\n",cas++);
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        cin >> s1 >> s2 >> res;
        bfs();
    }
    return 0;
}

文章作者: 保底不歪抽早柚
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 保底不歪抽早柚 !
评论
  目录