17年暑假复习(搜索篇)共11题

本文介绍了程序设计竞赛中涉及的深度优先搜索(DFS)、广度优先搜索(BFS)及穷竭搜索等算法的应用实例,包括《挑战程序设计竞赛》一书中的一些经典题目解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此次复习直接跟着《挑战程序设计竞赛》这本书走,既看例题复习,用通过后面的习题来进行练手。

DFS

POJ 1979:Red and Black

红与黑,算是深搜比较经典的题了,大意是求能走到的黑色格子有多少。从起点开一个dfs就可以,但是我还是习惯性地写bfs。
简单题直接上代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXW 25
#define MAXH 25
using namespace std;

int w,h;
int f[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
char map[MAXW][MAXH];
struct node
{
  int x,y;
}st;

int dfs(int x,int y)
{
    int cnt=0;
    map[x][y]='#';
    for(int i=0;i<4;i++)
    {
        int nx=x+f[i][0];
        int ny=y+f[i][1];
        if(map[nx][ny]=='#'||nx<1||ny<1||nx>w||ny>h)
            continue;
        cnt+=dfs(nx,ny);
    }
    return cnt+1;
}

void Init()
{
    memset(map,0,sizeof map);
    st.x=st.y=0;
}

int main()
{
    while(1)
    {
        Init();
        scanf("%d%d",&h,&w);
        if(!w)
            return 0;
        for(int i=1;i<=w;i++)
        {
            scanf("%s",map[i]+1);
            for(int j=1;j<=h;j++)
                if(map[i][j]=='@')
                    st.x=i,st.y=j;
        }
        printf("%d\n",dfs(st.x,st.y));
    }
}

AOJ 0118:Property Distribution

因为是日本人写的书,所以自然有些题是日文,但是绝大部分都可以在百度上找到翻译,此处不再复制。大意就是求有多少个区域,不同的水果相互4联通(即4个方向相邻算联通)算一个区域。
从头开始做dfs,把能拓展的点全部访问一遍标记vis,遇到已经标记的点就跳过,最后相当于统计做了多少次dfs。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXW 105
#define MAXH 105
using namespace std;

int w,h,ans;
int f[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
bool vis[MAXH][MAXW];
char map[MAXH][MAXW];

void dfs(int x,int y,char wh)
{
    vis[x][y]=1;
    for(int i=0;i<4;i++)
    {
        int nx=x+f[i][0];
        int ny=y+f[i][1];
        if(map[nx][ny]!=wh||vis[nx][ny]||nx<1||ny<1||nx>h||ny>w)
            continue;
        dfs(nx,ny,wh);
    }
}

void Init()
{
    memset(map,0,sizeof map);
    memset(vis,0,sizeof vis);
    ans=0;
}

int main()
{
    while(1)
    {
        Init();
        scanf("%d%d",&h,&w);
        if(!h)
            return 0;
        for(int i=1;i<=h;i++)
            scanf("%s",map[i]+1);
        for(int i=1;i<=h;i++)
            for(int j=1;j<=w;j++)
            {
                if(vis[i][j])
                    continue;
                ans++;
                dfs(i,j,map[i][j]);
            }
        printf("%d\n",ans);
    }
}

AOJ 0033:Ball

题目大意是将10个球通过挡板使其落向左或右,使得两边的球都是升序。
我的做法是直接进行dfs,使其满足升序的条件,然后检查最后的球是否能够放完,即为问题的解。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,ln,rn;
int a[15],l[15],r[15];

bool dfs(int x)
{
    if(x==11)
        return 1;
    bool flag=0;
    if(a[x]>l[ln])
    {
        l[++ln]=a[x];
        flag|=dfs(x+1);
        ln--;
    }
    if(a[x]>r[rn])
    {
        r[++rn]=a[x];
        flag|=dfs(x+1);
        rn--;
    }
    return flag;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        memset(l,0,sizeof l);
        memset(r,0,sizeof r);
        ln=rn=0;
        for(int j=1;j<=10;j++)
            scanf("%d",&a[j]);
        if(dfs(1))
            printf("YES\n");
        else
            printf("NO\n");
    }
}

POJ 3009:Curling 2.0

题目大意,有一个特殊的冰壶,它会沿冰面一直运动到它撞到一个障碍,此时障碍小时,并且你可以使其向另一个方向运动(可以是之前相同的方向),除此之外冰壶没有办法停下。最终要求你找到使冰壶从起点到终点的最小步数。
我认为这道题是一个普通的dfs,只不过在进行状态转移的时候将向周围拓展一个改为拓展到障碍为止。另外为了方便还原现场,可以用栈来保存一下去除的障碍。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#define MAXW 25
#define MAXH 25
#define INF 0x3fffffff
using namespace std;

int w,h,ans;
int map[MAXH][MAXW];
int f[4][2]={{0,-1},{0,1},{1,0},{-1,0}};
struct node
{
    int x,y;
    node(){}
    node(int a,int b)
    {
        x=a,y=b;
    }
}st;
stack <node> dis;

int run1(int x,int y,int i)
{
    if(i==0)
        return x;
    if(map[x+i][y]==1)
        return -1;
    while(1)
    {
        int nx=x+i;
        if(nx<1||nx>h)
            return -1;
        if(map[nx][y]==1)
        {
            dis.push(node(nx,y));
            map[nx][y]=0;
            return x;
        }
        if(map[nx][y]==3)
            return 0;
        x=nx;
    }
}

int run2(int x,int y,int i)
{
    if(i==0)
        return y;
    if(map[x][y+i]==1)
        return -1;
    while(1)
    {
        int ny=y+i;
        //printf("***%d\n",ny);
        if(ny<1||ny>w)
            return -1;
        if(map[x][ny]==1)
        {
            dis.push(node(x,ny));
            map[x][ny]=0;
            return y;
        }
        if(map[x][ny]==3)
            return 0;
        y=ny;
    }
}

void dfs(int x,int y,int cnt)
{
    //printf("*%d*%d*%d----\n",x,y,cnt  );
    if(cnt>=10)
        return;
    for(int i=0;i<4;i++)
    {
        int nx=run1(x,y,f[i][0]);
        int ny=run2(x,y,f[i][1]);
        //printf("**%d*%d*\n",nx,ny);
        if(nx==-1||ny==-1)
            continue;
        if(nx==0||ny==0)
        {
            ans=min(ans,cnt+1);
            return;
        }
        dfs(nx,ny,cnt+1);
        node t=dis.top();
        dis.pop();
        map[t.x][t.y]=1;
    }
    return;
}

int main()
{
    while(1)
    {
        ans=INF;
        memset(map,0,sizeof map);
        while(!dis.empty())
            dis.pop();
        scanf("%d%d",&w,&h);
        if(!h)
            return 0;
        for(int i=1;i<=h;i++)
            for(int j=1;j<=w;j++)
            {
                scanf("%d",&map[i][j]);
                if(map[i][j]==2)
                    st.x=i,st.y=j;
            }
        //printf("%d-%d\n",st.x,st.y);
        dfs(st.x,st.y,0);
        printf("%d\n",ans==INF?-1:ans);
    }
}

BFS

AOJ 0558: Cheese

题目大意,按照顺序访问1到n个点。
直接做n遍bfs,将得到的结果相加即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define MAXH 1005
#define MAXW 1005
using namespace std;

int h,w,n,ans;
int f[4][2]={{0,-1},{1,0},{-1,0},{0,1}};
char map[MAXH][MAXW];
struct node
{
  int x,y,cnt;
  node(){}
  node(int a,int b,int c)
  {
      x=a,y=b,cnt=c;
  }
}a[15];

void bfs(node s,node e)
{
    queue <node> q;
    bool vis[MAXH][MAXW];
    memset(vis,0,sizeof vis);
    q.push(s);
    vis[s.x][s.y]=1;
    while(!q.empty())
    {
        node t=q.front();
        //printf("--%d*%d\n",t.x,t.y);
        q.pop();
        for(int i=0;i<4;i++)
        {
            int nx=t.x+f[i][0];
            int ny=t.y+f[i][1];
            if(vis[nx][ny]||nx<1||ny<1||nx>h||ny>w||map[nx][ny]=='X')
                continue;
            if(nx==e.x&&ny==e.y)
            {
                ans+=t.cnt+1;
                return;
            }
            vis[nx][ny]=1;
            q.push(node(nx,ny,t.cnt+1));
        }
    }
}

int main()
{
    scanf("%d%d%d",&h,&w,&n);
    for(int i=1;i<=h;i++)
    {
        scanf("%s",map[i]+1);
        for(int j=1;j<=w;j++)
        {
            if(map[i][j]=='S')
                a[0].x=i,a[0].y=j;
            if(map[i][j]>='1'&&map[i][j]<='9')
                a[map[i][j]-'0'].x=i,a[map[i][j]-'0'].y=j;
        }
    }
    for(int i=1;i<=n;i++)
        bfs(a[i-1],a[i]);
    printf("%d\n",ans);
}

POJ 3669:Meteor Shower

题目大意,将会有一些流星砸向地面,你需要找到一个安全的地方,即永远不会被流星砸到的地方。但是被流星摧毁的地方你是不能走的。流星会在某个时刻摧毁它所坠落的一格以及相邻的四格,求到达安全点的最短时间。
可以先将会被流星摧毁的格子信息保存起来,而存的东西就是流星摧毁此处的时间。然后在进行bfs时,通过到达此点的时间和此点被摧毁的时间进行比较,判断是否可以通过。然而要特别注意,题目中并没有说必须移动,所以需要增加一个保持原地不动的状态,或者对原点进行特殊判断。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define INF 0x3fffffff
using namespace std;

int n;
int map[405][405];
int f[5][2]={{1,0},{-1,0},{0,1},{0,-1},{0,0}};
struct node
{
  int x,y,cnt;
  node(){}
  node(int a,int b,int c)
  {
      x=a,y=b,cnt=c;
  }
};

void bfs()
{
    queue <node> q;
    bool vis[405][405];
    memset(vis,0,sizeof vis);
    q.push(node(0,0,0));
    vis[0][0]=1;
    while(!q.empty())
    {
        node t=q.front();
        //printf("**%d-%d-%d\n",t.x,t.y,t.cnt);
        q.pop();
        if(map[t.x][t.y]==INF)
        {
            printf("%d",t.cnt);
            return;
        }
        if(map[t.x][t.y]<=t.cnt)
            continue;
        for(int i=0;i<5;i++)
        {
            int nx=t.x+f[i][0];
            int ny=t.y+f[i][1];
            if(nx<0||ny<0||nx>400||ny>400||vis[nx][ny]||t.cnt+1>=map[nx][ny])
                continue;
            vis[nx][ny]=1;
            q.push(node(nx,ny,t.cnt+1));
        }
    }
    printf("-1");
    return;
}

int main()
{
    for(int i=0;i<=405;i++)
        for(int j=0;j<=405;j++)
            map[i][j]=INF;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x,y,t;
        scanf("%d%d%d",&x,&y,&t);
        for(int j=0;j<5;j++)
        {
            int nx=x+f[j][0];
            int ny=y+f[j][1];
            if(nx<0||ny<0||nx>400||ny>400)
                continue;
            map[nx][ny]=min(map[nx][ny],t);
            //printf("*%d*%d\n",nx,ny);
        }
    }
    bfs();
    return 0;
}

AOJ 0121:Seven Puzzle

这道题可以说是八数码的弱化版。看到这道题是第一个反应是状压,然后就想到hash。但是仔细一想,8的8次方似乎可以接受,于是就用的普通的数位压缩。接着思考是在线做还是离线做,即问一次答一次或问完后一起达(请仔细思考这两种方法所用的时间)。在线肯定是会超时间的,而我脑残了,认为离线做的空间复杂度是也是8的8次方会爆内存,而实际上只有8的阶乘。最后恍然大悟,自学map做了离线。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;

map <int,int> vis;
int st;
int eg[9];
int a[8]={1,8,64,512,4096,32768,262144,2097152};
int f[4]={-1,4,1,-4};
struct node
{
    int state;
    int cnt;
    node(){}
    node(int a,int b)
    {
        state=a,cnt=b;
    }
};
queue <int> q;

int zy()
{
    int tot=0,cnt=0;
    for(int i=8;i>0;i--)
        {
            cnt=a[tot++]*eg[i]+cnt;
        }
    return cnt;
}

void jy(int x)
{
    int tot=7;
    for(int i=1;i<=8;i++)
        {
            eg[i]=x/a[tot];
            x%=a[tot--];
        }
}

void Init()
{
    q.push(342391);
    vis[342391]=0;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        jy(t);
        for(int i=1;i<=8;i++)
            if(eg[i]==0)
            {
                for(int j=0;j<4;j++)
                {
                    int nx=i+f[j];
                    if(nx<1||nx>8)
                        continue;
                    if((i==4&&nx==5)||(i==5&&nx==4))
                        continue;
                    swap(eg[i],eg[nx]);
                    //for(int qe=1;qe<=8;qe++)
                     //   printf("*%d",eg[qe]);
                    int ne=zy();
                    //printf("--%d",ne);
                    swap(eg[nx],eg[i]);
                    if(vis[ne]!=0||ne==342391)
                        continue;
                    vis[ne]=vis[t]+1;
                    q.push(ne);
                    //printf("\n");
                }
            }
    }
}

int main()
{
    Init();
    while(~scanf("%d",&eg[1]))
    {
        for(int i=2;i<=8;i++)
        scanf("%d",&eg[i]);
        int now=zy();
        printf("%d\n",vis[now]);
    }
}

穷竭搜索

POJ 2718:Smallest Difference

题目大意,给定一个集合,选其中一些数组成一个新的数,剩下的数组成另一个数,要求这两个数之差的绝对值最小,求这个最小值。
可以两个dfs跑,枚举出两个数,答案取min即可。但是这样做肯定会超时的,因此需要剪枝。我们可以发现,当我们构造第二个数时,如果已经构造的部分后面全部补上0所得到的答案已经不优于当前得到的解,那么就剪去。这个最优性剪枝可以砍掉一大半的时间。
另外听说贪心可以做出来,欢迎大家尝试。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define INF 0x3fffffff
using namespace std;

int n,cnt,best;
int a[15];
bool vis[15];

bool check(int x,int y,int t)
{
    if(y==0)
        return 1;
    int tot=1;
    for(int i=1;i<=t;i++)
        tot*=10;
    //printf("********%d\n",y*tot);
    if(abs(x-(y*tot))>best)
        return 0;
    return 1;
}

void dfs2(int x,int last,int num)
{
    if(!check(last,num,cnt-(cnt/2)-x))
    {
        return;
    }
    if(x==cnt-(cnt/2))
    {
        //printf("---%d\n",num);
        best=min(best,abs(num-last));
        return;
    }
    for(int i=1;i<=cnt;i++)
    {
        if(vis[i])
            continue;
        if(a[i]==0&&cnt!=2&&x==0)
            continue;
        vis[i]=1;
        dfs2(x+1,last,num*10+a[i]);
        vis[i]=0;
    }
}

void dfs1(int x,int num)
{
    if(x==cnt/2)
    {
        //printf("*%d\n",num);
        dfs2(0,num,0);
        return;
    }
    for(int i=1;i<=cnt;i++)
    {
        if(vis[i])
            continue;
        if(a[i]==0&&cnt!=2&&x==0)
            continue;
        vis[i]=1;
        dfs1(x+1,num*10+a[i]);
        vis[i]=0;
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        best=INF;
        memset(vis,0,sizeof vis);
        cnt=0;
        char c;
        while(1)
        {
            scanf("%d%c",&a[++cnt],&c);
            if(c=='\n')
                break;
        }
        dfs1(0,0);
        printf("%d\n",best);
    }

}

POJ 3187:Backward Digit Sums

题目大意,数字金字塔倒着来做。给定最终的结果和初始有几个数,求字典序最小的解。
我们可以倒着来解这道题,直接进行全排列,判断是否符合题意即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,sum;
int a[15];
bool vis[15];

bool check()
{
    int b[15];
    memcpy(b,a,sizeof a);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n-i;j++)
            b[j]=b[j]+b[j+1];
    //printf("**%d\n",b[1]);
    if(b[1]==sum)
        return 1;
    return 0;
}

void dfs(int x)
{
    if(x==n+1)
    {
        if(check())
        {
            for(int i=1;i<n;i++)
                printf("%d ",a[i]);
            printf("%d",a[n]);
            exit(0);
        }
        return ;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i])
            continue;
        vis[i]=1;
        a[x]=i;
        dfs(x+1);
        vis[i]=0;
    }
}

int main()
{
    scanf("%d%d",&n,&sum);
    dfs(1);
}

POJ 3050:Hopscotch

题目大意,给定5*5的地图,走6步,求得到的不同序列有多少种。
dfs直接搜,判重用数位压缩一下,压成一个6位数进行判重就好了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int map[10][10],ans;
int f[4][2]={{0,-1},{0,1},{-1,0},{1,0}};
bool vis[1000000],last[10][10];

void dfs(int x,int y,int cnt,int num)
{
    if(cnt==6)
    {
        //printf("--%d--%d\n",num,ans);
        if(!vis[num])
            ans++;
        vis[num]=1;
        return;
    }
    for(int i=0;i<4;i++)
    {
        int nx=x+f[i][0];
        int ny=y+f[i][1];
        if(nx<1||ny<1||nx>5||ny>5)
            continue;
        dfs(nx,ny,cnt+1,num*10+map[nx][ny]);
    }
}

int main()
{
    for(int i=1;i<=5;i++)
        for(int j=1;j<=5;j++)
            scanf("%d",&map[i][j]);
    for(int i=1;i<=5;i++)
        for(int j=1;j<=5;j++)
            dfs(i,j,0,0);
    printf("%d\n",ans);
}

AOJ 0525:Osenbei

题目大意,给定一些烧饼,你可以同时翻一列,或同一行,使得最后朝上的烧饼数最多,求这个最大值。
因为数据范围的特殊性,我们可以01枚举行翻还不翻。然后统计纵向统计这个状态下,面朝上的烧饼有多少个。因为操作不限制,所以取一个max就可以保证是最多的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int r,c,best;
int map[15][10005];

int num()
{
    int sum=0;
    for(int j=1;j<=c;j++)
    {
        int cnt=0;
        for(int i=1;i<=r;i++)
        {
            if(map[i][j]==1)
                cnt++;
        }
        sum+=max(cnt,r-cnt);
    }
    return sum;
}

void work(int a)
{
    for(int i=1;i<=c;i++)
        map[a][i]=map[a][i]==0?1:0;
}

void dfs(int x)
{
    if(x==r+1)
    {
        best=max(best,num());
        return;
    }
    work(x);
    dfs(x+1);
    work(x);
    dfs(x+1);
}

int main()
{
    while(1)
    {
        scanf("%d%d",&r,&c);
        if(!r)
            return 0;
        best=0;
        for(int i=1;i<=r;i++)
            for(int j=1;j<=c;j++)
                scanf("%d",&map[i][j]);
        dfs(1);
        printf("%d\n",best);
    }
}
基于Spring Boot搭建的一个多功能在线学习系统的实现细节。系统分为管理员和用户两个主要模块。管理员负责视频、文件和文章资料的管理以及系统运营维护;用户则可以进行视频播放、资料下载、参与学习论坛并享受个性化学习服务。文中重点探讨了文件下载的安全性和性能优化(如使用Resource对象避免内存溢出),积分排行榜的高效实现(采用Redis Sorted Set结构),敏感词过滤机制(利用DFA算法构建内存过滤树)以及视频播放的浏览器兼容性解决方案(通过FFmpeg调整MOOV原子位置)。此外,还提到了权限管理方面自定义动态加载器的应用,提高了系统的灵活性和易用性。 适合人群:对Spring Boot有一定了解,希望深入理解其实际应用的技术人员,尤其是从事在线教育平台开发的相关从业者。 使用场景及目标:适用于需要快速搭建稳定高效的在线学习平台的企业或团队。目标在于提供一套完整的解决方案,涵盖从资源管理到用户体验优化等多个方面,帮助开发者更好地理解和掌握Spring Boot框架的实际运用技巧。 其他说明:文中不仅提供了具体的代码示例和技术思路,还分享了许多实践经验教训,对于提高项目质量有着重要的指导意义。同时强调了安全性、性能优化等方面的重要性,确保系统能够应对大规模用户的并发访问需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值