蓝桥杯备赛(五) 双指针,BFS与图论

蓝桥杯备赛(五) 双指针,BFS与图论

一、双指针

1.Acwing 1238. 日志统计
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。
其中每一行的格式是:
ts id
表示在 ts 时刻编号 id 的帖子收到一个”赞”。
现在小明想统计有哪些帖子曾经是”热帖”。
如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。
具体来说,如果存在某个时刻 TT 满足该帖在 [T,T+D)这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。
给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。
输入格式
第一行包含三个整数 N,D,K。
以下 N 行每行一条日志,包含两个整数 ts和 id。
输出格式
按从小到大的顺序输出热帖 id。
每个 id 占一行。
数据范围
1≤K≤N≤105
0≤ts,id≤105
1≤D≤10000
输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3

暴力做法(会超时)

#include 
#include 
#include 
#include 

using namespace std;

const int N=100010;
pair <int,int> logs[N];
int cnt[N]; //计数
bool st[N];
int n,d,k;
int main()
{
    
    scanf("%d%d%d",&n,&d,&k);
    for(int i=0;i<n;i++) scanf("%d%d",&logs[i].first,&logs[i].second);
    sort(logs,logs+n);

    for(int i=0;i<n;i++) //i一个个循环,j在时长小于d的范围内移动
    {
        memset(cnt,0,sizeof(cnt));
        int m=0; //用来计数
        for(int j=i;;j++)  //时长超过d停止
        {
            m=logs[j].first-logs[i].first;
            if(m>=d||m<0) break;
            int id=logs[j].second;
            cnt[id]++;
            if(cnt[id]>=k)
                st[id]=true;
        }
    }
    for (int i = 0; i < N; i++)
        if (st[i]) 
            printf("%d\n", i);
    getchar();getchar();
    return 0;
}

利用双指针算法对暴力算法优化,双指针算法即有i,j两个指针,根据题意对原本的两个for优化。本题中,对于每次j在d范围内的循环,其实只是在上一次的基础上前面加一个,后面减一个,不用从头开始循环。

#include 
#include 
#include 
#include 

using namespace std;

const int N=100010;
pair <int,int> logs[N];
int cnt[N]; //计数
bool st[N];
int n,d,k;
int main()
{
    
    scanf("%d%d%d",&n,&d,&k);
    for(int i=0;i<n;i++) scanf("%d%d",&logs[i].first,&logs[i].second);
    sort(logs,logs+n);

    for(int i=0,j=0;i<n;i++) //这里是i先移动,j停留在原地
    {
        int id=logs[i].second;
        cnt[id]++;

        while(logs[i].first-logs[j].first>=d)
        {
            cnt[logs[j].second]--;
            j++;     //当超过指定时间范围d后,j再移动
        }
        if(cnt[id]>=k)
            st[id]=true;
        
    }
    for (int i = 0; i < N; i++)
        if (st[i]) 
            printf("%d\n", i);
    getchar();getchar();
    return 0;
}

二、BFS

队列----先进先出-----BFS—宽搜
栈-------先进后出-----DFS—深搜

BFS较DFS的特点是,可找到步数最小的合法路径
BFS原则:每次取出队头元素,将拓展出的所有元素放到队尾

伪代码:

queue <-初始状态      //起点入队
while(queue非空)
{
    t=队头元素
    for(拓展t节点)
    {
        ver =新节点
        if(!st[ver])   //判重
        {
            ver ->队尾    //ver入队,放在队尾
        }
    }
}

1.Acwing 1101. 献给阿尔吉侬的花束
阿尔吉侬是一只聪明又慵懒的小白鼠,它最擅长的就是走各种各样的迷宫。
今天它要挑战一个非常大的迷宫,研究员们为了鼓励阿尔吉侬尽快到达终点,就在终点放了一块阿尔吉侬最喜欢的奶酪。
现在研究员们想知道,如果阿尔吉侬足够聪明,它最少需要多少时间就能吃到奶酪。
迷宫用一个 R×C的字符矩阵来表示。
字符 S 表示阿尔吉侬所在的位置,字符 E 表示奶酪所在的位置,字符 # 表示墙壁,字符 . 表示可以通行。
阿尔吉侬在 1 个单位时间内可以从当前的位置走到它上下左右四个方向上的任意一个位置,但不能走出地图边界。
输入格式
第一行是一个正整数 T,表示一共有 T 组数据。
每一组数据的第一行包含了两个用空格分开的正整数 R 和 C,表示地图是一个 R×C的矩阵。
接下来的 R 行描述了地图的具体内容,每一行包含了 C 个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 E。
输出格式
对于每一组数据,输出阿尔吉侬吃到奶酪的最少单位时间。
若阿尔吉侬无法吃到奶酪,则输出“oop!”(只输出引号里面的内容,不输出引号)。
每组数据的输出结果占一行。
数据范围
1 2≤R,C≤200
输入样例:
3
3 4
.S…
###.
…E.
3 4
.S…
.E…

3 4
.S…

…E.
输出样例:
5
1
oop!

#include 
#include 
#include 
#include 
#include

using namespace std;

const int N=210;
int n,m;
char g[N][N];
int dist[N][N]; //同时具有判重作用与记录作用

int bfs(pair<int,int> start,pair<int,int> end)
{
    queue<pair<int,int>> q;  //队列q是二元的,有两个元素,即横纵坐标
    memset(dist,-1,sizeof(dist));
    dist[start.first][start.second]=0;
    q.push(start); //起点入队

    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //偏移量,即上下左右移动的dx,dy

    while(q.size())
    {
        pair<int,int> t=q.front(); //取出队头元素

        //弹出队头
        q.pop();

        for(int i=0;i<4;i++)
        {
            int x=t.first+dx[i],y=t.second+dy[i];

            //约束条件
            if(x<0||x>=n||y<0||y>=m) continue; //出界
            if(dist[x][y]!=-1) continue; //走过
            if(g[x][y]=='#') continue; //撞到障碍

            dist[x][y]=dist[t.first][t.second]+1; //步数加一

            if(end==make_pair(x,y)) return dist[x][y]; //走到终点,返回距离
            //make_pair生成一个pair
            q.push({x,y});  //符合要求,插入队尾
        }
    }
    return -1; //若没到找到路径,返回-1
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++) scanf("%s",g[i]); //读入

        pair<int,int> start,end;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                if(g[i][j]=='S') start={i,j};  //起点
                else if(g[i][j]=='E') end={i,j};  //终点
            }
        int distance=bfs(start,end);  //bfs函数输入的参数为起点与终点
        if(distance==-1) puts("oop!");
        else printf("%d\n",distance);
    }
    getchar();getchar();
    return 0;
}

2.Acwing 1096. 地牢大师
你现在被困在一个三维地牢中,需要找到最快脱离的出路!
地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。
向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。
你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。
请问,你有可能逃脱吗?
如果可以,需要多长时间?
输入格式
输入包含多组测试数据。
每组数据第一行包含三个整数 L,R,C 分别表示地牢层数,以及每一层地牢的行数和列数。
接下来是 L个 R 行 C列的字符矩阵,用来表示每一层地牢的具体状况。
每个字符用来描述一个地牢单元的具体状况。
其中, 充满岩石障碍的单元格用”#”表示,不含障碍的空单元格用”.”表示,你的起始位置用”S”表示,终点用”E”表示。
每一个字符矩阵后面都会包含一个空行。
当输入一行为”0 0 0”时,表示输入终止。
输出格式
每组数据输出一个结果,每个结果占一行。
如果能够逃脱地牢,则输出”Escaped in x minute(s).”,其中X为逃脱所需最短时间。
如果不能逃脱地牢,则输出”Trapped!”。
数据范围
1≤L,R,C≤1001≤L,R,C≤100
输入样例:
3 4 5
S…
.###.
.##…
###.#

##.##
##…

#.###
####E

1 3 3
S##
#E#

0 0 0
输出样例:
Escaped in 11 minute(s).
Trapped!

#include
#include
#include
#include
#include
using namespace std;

const int N=110;
int l,r,c;
char g[N][N][N];
int dist[N][N][N];
bool visited[N][N][N];
int dx[6] = {1, -1, 0, 0, 0, 0};
int dy[6] = {0, 0, 1, -1, 0, 0};
int dz[6] = {0, 0, 0, 0, 1, -1};
struct point  //因为是三维的,所以用结构体
{
    int x,y,z;
};
int bfs(point start,point end)
{
    queue<point> q;
    memset(dist,-1,sizeof(dist));
    memset(visited,0,sizeof(visited));
    dist[start.x][start.y][start.z]=0;
    q.push(start);  //起点入队

    while(!q.empty())
    {
        point t=q.front();  //取出队头元素
        q.pop();            //弹出队头
        for(int i=0;i<6;i++)
        {
            int tx=t.x+dx[i];
            int ty=t.y+dy[i];
            int tz=t.z+dz[i];
            if(tx<0||tx>=l||ty<0||ty>=r||tz<0||tz>=c) continue; //边界
            if(visited[tx][ty][tz]) continue;    //走过
            if(g[tx][ty][tz]=='#') continue;    //障碍

            //更新
            dist[tx][ty][tz]=dist[t.x][t.y][t.z]+1;  
            visited[tx][ty][tz]=true;

            
            if(end.x==tx&&end.y==ty&&end.z==tz) return dist[tx][ty][tz];
            q.push({tx,ty,tz});     //符合要求,插入队尾
        }
    }
    return -1;    //若没到找到路径,返回-1
}
int main()
{
    while(cin>>l>>r>>c,l||r||c)
    {
        //输入
        for(int i=0;i<l;i++)
            for(int j=0;j<r;j++)
                scanf("%s",g[i][j]);
        
        point start,end;
        for(int i=0;i<l;i++)
            for(int j=0;j<r;j++)
                for(int k=0;k<c;k++)
                {
                    char c=g[i][j][k];
                    if(c=='S') start={i,j,k};
                    else if(c=='E') end={i,j,k};
                }
        int distance=bfs(start,end);
        if (distance == -1) puts("Trapped!");
        else printf("Escaped in %d minute(s).\n", distance);
    }
    getchar();getchar();
    return 0;
}

三、DFS

本题为flood fill中的DFS解法
1.Acwing 1113. 红与黑
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
输入包括多个数据集合。
每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y方向瓷砖的数量。
在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:白色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
数据范围
1≤W,H≤201≤W,H≤20
输入样例:
6 9
…#.
…#





#@…#
.#…#.
0 0
输出样例:
45

#include
#include
#include
#include
using namespace std;

const int N=25;
int n,m;
char g[N][N];
bool st[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};  //四个方向
int dfs(int x,int y)
{
    int cnt=1;
    st[x][y]=true;
    for(int i=0;i<4;i++)
    {
        int a=x+dx[i],b=y+dy[i];
        if(a<0||a>=n||b<0||b>=m) continue;
        if(g[a][b]!='.') continue;
        if(st[a][b]) continue;

        cnt+=dfs(a,b);
    }
    return cnt;
}
int main()
{
    while(cin>>m>>n)
    {
        if(m==0||n==0) break;
        int x,y;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                cin>>g[i][j];
                if(g[i][j]=='@')
                {
                    x=i;
                    y=j;
                }
            }
        memset(st,0,sizeof(st)); //清空上一次的状态
        cout<<dfs(x,y)<<endl;
    }
    
    getchar();getchar();
    return 0;
}

2.Acwing 1233. 全球变暖
你有一张某海域 N×N像素的照片,”.”表示海洋、”#”表示陆地,如下所示:

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

其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:




…#…


请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入格式
第一行包含一个整数N。
以下 NN 行 NN 列,包含一个由字符”#”和”.”构成的 N×N 字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。
照片保证第 11 行、第 11 列、第 NN 行、第 NN 列的像素都是海洋。
输出格式
一个整数表示答案。
数据范围
1≤N≤1000
输入样例1:
7

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

输出样例1:
1
输入样例2:
9

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

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

输出样例2:
1

#include
#include
#include
#include
#include
using namespace std;

const int N=1010;
int n;
char g[N][N];
bool st[N][N];
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
void bfs(int xx,int yy,int &sum,int &bound)
{
    st[xx][yy]=true;
    queue<pair<int,int>> q;
    q.push({xx,yy});

    while(!q.empty())  //当栈为空时才结束循环
    {
        pair<int,int> t=q.front();
        q.pop();

        sum++;
        bool is_bound=false;
        for(int i=0;i<4;i++)
        {
            int x=t.first+dx[i],y=t.second+dy[i];
            if(x<0||x>=n||y<0||y>=n) continue;
            if(st[x][y]) continue;
            if(g[x][y]=='.')
            {
                is_bound=true;
                continue;
            }
            st[x][y]=true;
            q.push({x,y});
        }
        if(is_bound) bound++;
    }
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) scanf("%s",g[i]);
    int cnt=0;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            if(g[i][j]=='#'&&!st[i][j])  //当遍历到土地且未走过时
            {
                int sum=0,bound=0;
                bfs(i,j,sum,bound);
                if(sum==bound) cnt++; //沉没量与土地量相等,证明该岛屿全部沉没
            }
    cout<<cnt;
    getchar();getchar();
    return 0;
}

3.DFS+树的直径
Acwing 1207. 大臣的旅费
很久以前,T王国空前繁荣。
为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。
同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J是T国重要大臣,他巡查于各大城市之间,体察民情。
所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。
他有一个钱袋,用于存放往来城市间的路费。
聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入格式
输入的第一行包含一个整数 n,表示包括首都在内的T王国的城市数。
城市从 1 开始依次编号,1 号城市为首都。
接下来 n−1n−1 行,描述T国的高速路(T国的高速路一定是 n−1n−1 条)。
每行三个整数 Pi,Qi,Di,表示城市 Pi和城市 Qi之间有一条双向高速路,长度为 Di千米。
输出格式
输出一个整数,表示大臣J最多花费的路费是多少。
数据范围
1≤n≤105,
1≤Pi,Qi≤n,
1≤Di≤1000
输入样例:
5
1 2 2
1 3 1
2 4 5
2 5 4
输出样例:
135
蓝桥杯备赛(五) 双指针,BFS与图论_第1张图片

#include
#include
#include
#include
#include
using namespace std;

const int N=100010;
int n;
struct edge
{
    int id,w;  //存放编号与权值
};
vector<edge> h[N];  
//vector存放与i节点相连的节点,每个单元包括与之相连的的序号与权值
int dist[N];

//father表示父结点的编号,u表示当前结点的编号
void dfs(int u,int father,int distance) 
{
    dist[u]=distance;
    for(int i=0;i<h[u].size();i++)
    {
        int j = h[u][i].id ;
        if(j != father)
            dfs(j,u,distance+h[u][i].w) ;
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n-1;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        h[a].push_back({b,c});   //邻接表存储图
        h[b].push_back({a,c});
    }
    
    dfs(1,-1,0);  //dfs函数用来求当前节点到所有节点的距离
    int u=1;
    for(int i=1;i<=n;i++)
        if(dist[i]>dist[u])
            u=i;
    
    dfs(u,-1,0);
    for(int i=0;i<=n;i++)
        if(dist[i]>dist[u])
            u=i;
    
    int s=dist[u];
    printf("%lld\n",s*10+s*(s+1ll)/2); //这里的1ll表示1是longlong型的
    getchar();getchar();
    return 0;
}

四、图论,置换圈

图论,置换圈
Acwing 1224. 交换瓶子
有 N个瓶子,编号 1∼N,放在架子上。
比如有 55 个瓶子:
2 1 3 5 4
要求每次拿起 22 个瓶子,交换它们的位置。
经过若干次后,使得瓶子的序号为:
1 2 3 4 5
对于这么简单的情况,显然,至少需要交换 22 次就可以复位。
如果瓶子更多呢?你可以通过编程来解决。
输入格式
第一行包含一个整数 N,表示瓶子数量。
第二行包含 N 个整数,表示瓶子目前的排列状况。
输出格式
输出一个正整数,表示至少交换多少次,才能完成排序。
数据范围
1≤N≤10000,
输入样例1:
5
3 1 2 5 4
输出样例1:
3
输入样例2:
5
5 4 3 2 1
输出样例2:
2
蓝桥杯备赛(五) 双指针,BFS与图论_第2张图片
蓝桥杯备赛(五) 双指针,BFS与图论_第3张图片
蓝桥杯备赛(五) 双指针,BFS与图论_第4张图片

#include 
#include
#include
#include
using namespace std;

const int N=10010;
int a[N];
int n,k;   //k表示圈数
bool st[N];  //用于判重

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    for(int i=1;i<=n;i++)
    {
        if(!st[i])  //统计圈数
            k++;
        for(int j=i;!st[j];j=a[j]) //对于一个圈内的元素全部变为true
            st[j]=true;
    }
    cout<<n-k;
    getchar();getchar();
    return 0;
}

Acwing 1240. 完全二叉树的权值
给定一棵包含 N 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是 A1,A2,⋅⋅⋅AN,如下图所示:

现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?
如果有多个深度的权值和同为最大,请你输出其中最小的深度。
注:根的深度是 1。
输入格式
第一行包含一个整数 N。
第二行包含 N 个整数 A1,A2,⋅⋅⋅AN。
输出格式
输出一个整数代表答案。
数据范围
1≤N≤105,
−105≤Ai≤105
输入样例:
7
1 6 5 4 3 2 1
输出样例:
2

#include
#include
#include
#include
using namespace std;

const int N=100000;
int n;
int a[N];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    
    int d=0;     //表示深度
    long long smax=-1e18;
    int depth=0;
    for(int i=1;i<=n;i=i*2)
    {
        d++;
        long long s=0; 
        for(int j=i;j<1<<d&&j<=n;j++)  //这里注意最后一层可能不满,所以需要j<=n
            s=s+a[j];
        if(s>smax)
        {
            smax=s;
            depth=d;
        }
    }
    printf("%d\n",depth);
    getchar();getchar();
    return 0;
}

你可能感兴趣的:(算法)