几种典型的搜索题型(一)

搜索题型很多.但身为菜鸟的我,仅仅只能挑出一些常见的简单题型来讲解一番了。
因为不懂什么专业的术语,下面题型都是我自己命名,请不要见怪哦。

注:下面的代码头文件基本都是<bits/stdc++.h>,但有些刷题网站并不支持该文件头。

第一种题型 合并周边同类问题

一、合并周边同类问题基础版——Oil Deposits
题意:
有一块空地,‘@’代表油田,’*’不是油田。然后若是两个油田横竖斜三个方向中有一个方向是相连接的那么把这两个油田看做一个油田。
输入:
n, m和矩阵字符串,以0 0结束 (1<=n,m<=100)
输出
一共有几个油田

代码如下:


#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
char mat[maxn][maxn];
//next数组用于存入八个方向{{0,1}, {0,-1}......}这样看就清晰明了了。
int Next[8][2] = {0,1, 0,-1, 1,0, 1,1, 1,-1, -1,0, -1,1, -1,-1};
int n, m;
//用深度搜索探查油田
void dfs(int x, int y);

int main()
{
    while(cin>>n>>m &&n+m)
    {
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                cin>>mat[i][j];

        int ans = 0;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                if(mat[i][j] == '@')
                {
                    ans++;
                    dfs(i, j);
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

void dfs(int x, int y)
{
    //把当前符号改为'*',以便下次不用探查了
    mat[x][y] = '*';

    //延伸当前能接触的八个方位
    for(int i=0; i<8; i++)
    {
        //(tx,ty)存储八个方位的位置
        int tx = x + Next[i][0];
        int ty = y + Next[i][1];
        //无法越界
        if(tx<1 || tx>n || ty<1 || ty>m) continue;
        //发现是油田,立马去探查一番
        if(mat[tx][ty] == '@')
            dfs(tx, ty);
    }
}

二、合并周边同类问题升级版——Island问题 Gym - 101291H
题意:
海上航拍照片有三种状况,分别是岛屿’L’、海水’W’、还有被云雾遮挡的未知地方’C’。未知地方可能是岛,也可能是海水。把共有一边的岛屿看作是一个岛。求最小岛屿的数量。
输入:
n, m和矩阵字符串(1<=n,m<=50)
输出
一共有几个岛屿。
如:
Input
3 2
LW
CC
WL
Output
1

这道题不定因素是’C’部分,不过大体代码不改变,遇到岛屿开始深搜并且岛屿数量加一,在深搜中只要不是’W’就可以继续进行深搜。代码如下:


#include <bits/stdc++.h>
using namespace std;
const int maxn = 101;
char mat[maxn][maxn];
int next[4][2] = {0,1, 0,-1, 1,0, -1,0};
int n, m;
void dfs(int x, int y);

int main()
{
    while(cin>>n>>m &&n+m)
    {
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                cin>>mat[i][j];

        int ans = 0;
        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                //发现当前位置是岛屿则数量加一,且进行深搜探查一番
                if(mat[i][j] == 'L')
                {
                    ans++;
                    dfs(i, j);
                }
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

void dfs(int x, int y)
{
    mat[x][y] = 'W';
    for(int i=0; i<4; i++)
    {
        int tx = x + next[i][0];
        int ty = y + next[i][1];
        if(tx<1 || tx>n || ty<1 || ty>m) continue;
        //当不是海水就开始探查一番
        if(mat[tx][ty] != 'W')
            dfs(tx, ty);
    }
}

三、合并周边同类问题豪华版——Coconuts
题意:
有一吃货做梦,梦里有一地方,满格都是椰子,椰子有好有坏。吃货一次性可以吃了所有想连接的好椰子,即横竖方向上若是有好椰子相连接,都可以一次吃完。求吃货需要花几次吃完所有的椰子。
输入
测试的案例数T
每个案例要求输入行R、列C和坏椰子的数量n
(0 < R, C <= 1e9、0<= n <= 200)
输出
吃了几次,并且把每次吃的椰子数从小到大输出。

看到行和列的范围就头疼了,这更本无法直接建立一个二维数组存储数据。
于是一个新的方法出来了——离散化(discretization)!!!

那么什么是离散化?在此不多讲解,离散化详解传送地址——什么是离散化?

可以看出,坏椰子的个数在200以内,数量少,故而,可以从坏椰子的坐标入手,把坏椰子的所有坐标的 x 和 y 分别进行离散化,然后还需要链接好每个离散化后的 x 和 y ,这样就构造成了离散化后的坐标。即需要构造两个数组x[]和y[]。
如何离散一个某一方向上的点,拿x轴方向来说。
如果x[i] = x[i-1],那么就就离散到x[i-1]同一行;
如果x[i] = x[i-1] + 1,那么就离散到x[i-1]对应的下一行;
其他情况则离散到x[i-1]对应的下一行的下一行。
离散x轴的图如下:
这里写图片描述

设每一段的长度为dx,但每段或虚或实。
实线段的长度dx = 1;
虚线段的长度dx = x[i] - x[i-1] - 1。即两个点的距离,再减去一个实线段dx的长度就是虚线dx的长度。
这就是离散化x轴方向坐标操作,y轴也是同样如此。
具体实现代码如下:


#include <bits/stdc++.h>
using namespace std;

#define ll long long
const int maxn = 405;

//定义结构体,v代表离散前的坐标值,p代表离散后的坐标值
//id是点坐标的标记,方便离散后可以连接x与y,成功构造出一个离散点
struct Node
{
    int v, p;
    int id;
}x[maxn], y[maxn];
//用于区分好坏椰子坐标
bool mark[maxn][maxn];
//离散化后x轴y轴每条线段的长度
ll dx[maxn], dy[maxn];
int Next[4][2] = {0,1, 0,-1, 1,0, -1,0};
ll r, c;
int n;
//cmp1排序好离散前的坐标值,cmp2是用来排序id以便连接出离散点
bool cmp1(Node p1, Node p2);
bool cmp2(Node p1, Node p2);
//离散化函数,参数s,放入x轴或y轴方向的坐标数组,参数ds放入线段长度素组,len为某方向上的范围。
void disc(Node* s, ll* ds, ll &len);
//合并周边同类问题必写的递归,稍微改变一番
ll dfs(int x, int y);

int main()
{
    int T;
    cin>>T;
    for(int cas=1; cas<=T; cas++)
    {
        cin>>r>>c>>n;
        for(int i=1; i<=n; i++)
        {
            cin>>x[i].v>>y[i].v;
            x[i].id = y[i].id = i;
        }
        sort(x+1, x+n+1, cmp1);
        sort(y+1, y+n+1, cmp1);

        disc(x, dx, r);
        disc(y, dy, c);

        sort(x+1, x+n+1, cmp2);
        sort(y+1, y+n+1, cmp2);

        memset(mark, false, sizeof(mark));
        //把坏椰子打上标记
        for(int i=1; i<=n; i++)
            mark[x[i].p][y[i].p] = true;

        int cur = 0;
        ll ans[maxn];
        for(ll i=1; i<=r; i++)
            for(ll j=1; j<=c; j++)
                if(!mark[i][j]) ans[cur++] = dfs(i, j);
        sort(ans, ans+cur);

        printf("Case #%d:\n", cas);
        cout<<cur<<endl;
        for(int i=0; i<cur; i++)
        {
            if(i) cout<<" "<<ans[i];
            else cout<<ans[i];
        }
        cout<<endl;

    }
    return 0;
}
bool cmp1(Node p1, Node p2)
{
    return p1.v < p2.v;
}
bool cmp2(Node p1, Node p2)
{
    return p1.id < p2.id;
}
void disc(Node* s, ll* ds, ll &len)
{
    s[0].v = 1; s[0].id = 0;
    s[n+1].v = len; s[n+1].id = n+1;
    int cur = 1;
    ds[1] = 1;
    //注意,一定要到n+1处,这是为了把最后一点到范围边界处那段距离离散化
    for(int i=1; i<=n+1; i++)
    {
        if(s[i].v == s[i-1].v)
        {
            s[i].p = cur;
        }else if(s[i].v == s[i-1].v + 1)
        {
            s[i].p = cur + 1;
            ds[++cur] = 1;
        }else
        {
            s[i].p = cur + 2;
            ds[cur+1] = s[i].v - s[i-1].v - 1;
            ds[cur+2] = 1;
            cur += 2;
        }
    }
    //原来的范围变为离散化后的范围
    len = cur;
}
ll dfs(int x, int y)
{
    ll sum = dx[x] * dy[y];
    mark[x][y] = 1;
    for(int i=0; i<4; i++)
    {
        int tx = x + Next[i][0];
        int ty = y + Next[i][1];

        if(tx<1 || tx>r || ty<1 || ty>c) continue;
        if(!mark[tx][ty])
            sum += dfs(tx,ty);
    }
    return sum;
}

呼终于打完这段代码了,特别长,敲得头晕。不过收获不小。
好了以上三题是我至今遇到的几个典型的合并周边同类问题。这一类的问题暂且先写到此处,若是以后遇到其他典型的题,我会适当修整博客。
这篇博客已经很长了,姑且写到这里,余下的题型将会抽空补齐。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值