[AcWing蓝桥杯]之双指针,BFS,图论(C++题解)

目录

双指针

日志统计(双指针+滑动窗口)

完全二叉树的权值

BFS(经典迷宫)

献给阿尔吉侬的花束

红与黑

地牢大师

 全球变暖

图论

交换瓶子

暴力解法:

连通块(用find函数寻找公共祖先)

巧妙遍历


双指针


日志统计(双指针+滑动窗口)

1238. 日志统计 - AcWing题库

题目概述:输入日志的编号和点赞时刻,求D时间内,满足有K个赞的帖子的数量

输入第一行:

N:日志行数(可理解为操作次数) 时间:D(时间间隔) K:条件(大于等于K则满足条件)

接下来的N行:

first:点赞的时刻 second:点赞帖子的编号


思路解析:

<1>预处理

(1)由于接下来的n行,会输入点赞时刻和点赞的帖子,由于这两个是成对出现的,不难想到要把这两个合在一起,那么就需要pair<int,int>

(2)由于题目要求的是,满足条件的帖子的数量,那么必然需要一个判断数组st,来记录满足条件的帖子

(3)由上可推出,需要记录对应帖子的点赞个数,可推出需要一个计数数组cnt

 <2>核心思路

(1)排序

因为会有时间间隔的要求,所以需要对pair数组进行排序,这样,正序遍历的时候,才方便用双指针进行维护操作按照点赞的时刻顺序

对pair进行排序(默认以第一关键字为标准)

(2)遍历判断

按点赞时刻排好序之后,对pair进行遍历,同时取下它的编号,对应的:计数数组下标对应的标号对应的值+1

同时,对两次点赞时刻是否超过的D时间限制进行判断,如果超过了,那么就证明前面的j指针,指向的地方已经失效了(超过了时间间隔),那么就要将计数数组中对应的下标对应的值-1,同时j指针右移++,注意:操作顺序不能相反!!!

 <3>收尾判断

注意st数组的边界不是n,n代表的只是操作次数,因为要判断的是该编号对应的帖子是否满足条件,所以要遍历到它的编号范围为止

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int> PII;
#define x first
#define y second
const int N=100010;
PII nums[N];
int st[N];
int cnt[N];

int main()
{
    int n,d,k;
    cin>>n>>d>>k;
    
    for(int i=0;i<n;i++) scanf("%d%d",&nums[i].x,&nums[i].y);
    
    sort(nums,nums+n);
    
    for(int i=0,j=0;i<n;i++)
    {
        int t=nums[i].y;
        cnt[t]++;
        
        while(nums[i].x-nums[j].x>=d)
        {
            cnt[nums[j].y]--;
            j++;
        }
        if(cnt[t]>=k) st[t]=true;
    }
    
    for(int i=1;i<=N;i++)
    {
        if(st[i])
        {
            printf("%d\n",i);
        }
    }
    
    return 0;
}

完全二叉树的权值

1240. 完全二叉树的权值 - AcWing题库

传统的寻找最值的做法:

(1)在循环外定义一个答案变量res

(2)遍历每一种情况

(3)对每一种情况的和相加,再与答案变量进行比较

最后根据题意输出答案变量即可

 唯一需要注意的就是:

题目所说的是完全二叉树,那么就意味着每一层的起始位置和终止位置都有一个递推公式,具体看注释即可,然后还需特判一下,最后一层的终止位置是否大于了n(数组长度)

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
long long q[N];

int main()
{
	int n;
	cin >> n;

	for (int i = 1; i <= n; i++) cin >> q[i];

	int maxv = -1e18;

	int depth = 1;
	int res = 1;

	for (int i = 1; i <= n; i *= 2)//需要注意:小于n
	{
		long long s = 0;
		//完全二叉树 每层的开头为   2^(n-1)     结尾则是 2^n - 1
		for (int j = i; j <= i * 2 - 1 && j <= n; j++)
		{
			s += q[j];
		}
		if (s > maxv)
		{
			maxv = s;
			res = depth;
		}
		depth++;
	}
	cout << res << endl;

	return 0;
}

BFS(经典迷宫)

经典模板:

全局变量:

(1)定义pair<int,int>  第一个关键字存储横坐标,第二个关键字存储纵坐标

(2)定义queue队列

(3)定义存放地图g[N][N],也就是存放输入的字符串的数组

(4)定义dist数组:也就是该点到起点的距离数组

main函数中:

(1)遍历每一行每一列输入字符串,并输入,注意其实位置需要作为参数传给BFS,所以需要对其实位置用pair进行记录

(2)对于dist数组可以用memset进行初始化:(一般初始化为0或-1),反正都是会被覆盖的

(3)调用BFS函数

BFS函数中:

(1)将起始位置入队

(2)大条件循环(当队列不为空时继续操作)

<1>定义(x,y,z)方向的数组,具体看情况,表示在迷宫的生物能活动的范围

<2>遍历该生物能活动的范围,并用int a,b进行接收,如果g[a][b]不为墙壁或越界,那么就证明了a,b是一个可以走的点,那么就让dist[a][b]=dist[t.first][t.second]+1,(因为每次都可以向周围走1步,所以它到起点的距离==上一个点到起点的距离+1)

<3>如果该点可以走,那么就让该点入队,重复执行(2)内操作

<4>如果g[a][b]为终点,那么就证明可以走到终点,此时dist[终点,first][终点.second]一定为从起点到终点的最短距离

(3)跳出循环,队列为空,那么就证明了,从起始点无法到达终点,此时输出对应的语句


献给阿尔吉侬的花束

1101. 献给阿尔吉侬的花束 - AcWing题库

一点点不一样:

输入好起点之后,并用start进行保存下来,之后让起点位置变为墙壁,这样做的好处是:可以减少探索的次数,提升程序的效率,因为每一个点只可能被走一次,此时才可能是最短路!

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 210;

char a[N][N];
int dis[N][N];

void bfs(PII start)
{
	queue<PII> q;
	q.push(start);
	while (!q.empty())
	{
		PII u = q.front();
		q.pop();
		int dx[4] = { -1,0,1,0 };
		int dy[4] = { 0,1,0,-1 };
		for (int i = 0; i < 4; i++)
		{
			int x = u.first + dx[i];
			int y = u.second + dy[i];

			if (a[x][y] == '#') continue;
			if (a[x][y] == '.')
			{
				dis[x][y] = dis[u.first][u.second] + 1;
				a[x][y] = '#';
				q.push({ x,y });
			}
			if (a[x][y] == 'E')
			{
				cout << dis[u.first][u.second] + 1 << endl;
				return;
			}
		}
	}
	cout << "oop!" << endl;
}

int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		memset(a, '#', sizeof a);
		memset(dis, 0, sizeof dis);
		int n, m;
		PII start;
		cin >> n >> m;

		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				cin >> a[i][j];
				if (a[i][j] == 'S')
				{
					start.first = i;
					start.second = j;
					a[i][j] = '#';
				}
			}
		}
		bfs(start);
	}

	return 0;
}

红与黑

1113. 红与黑 - AcWing题库

没啥可讲,纯模板

#include<iostream>
#include<cstring>
using namespace std;
const int N=25;
char g[N][N];
bool st[N][N];
int n,m;
int dx[4]={-1,0,1,0};
int 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];
        int 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, n || m)
	{
		for (int i = 0; i < n; i++) cin >> g[i];

		int x, y;
		for(int i=0;i<n;i++)
			for (int j = 0; j < m; j++)
			{
				if (g[i][j] == '@')
				{
					x = i;
					y = j;
				}
			}
		memset(st, 0, sizeof st);
		cout << dfs(x, y) << endl;
	}

	return 0;
}

地牢大师

1096. 地牢大师 - AcWing题库

三维地图而已,只是多加了一个维度,和多判断条件而已

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

using namespace std;

const int N = 110;

struct Point
{
    int x, y, z;
};

int L, R, C;
char g[N][N][N];
Point q[N * N * N];
int dist[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 };

int bfs(Point start, Point end)
{
    int hh = 0, tt = 0;
    q[0] = start;
    memset(dist, -1, sizeof dist);
    dist[start.x][start.y][start.z] = 0;

    while (hh <= tt)
    {
        auto t = q[hh++];

        for (int i = 0; i < 6; i++)
        {
            int x = t.x + dx[i], y = t.y + dy[i], z = t.z + dz[i];
            if (x < 0 || x >= L || y < 0 || y >= R || z < 0 || z >= C) continue;  // 出界
            if (g[x][y][z] == '#') continue;  // 有障碍物
            if (dist[x][y][z] != -1) continue;  // 之前走到过

            dist[x][y][z] = dist[t.x][t.y][t.z] + 1;
            if (x == end.x && y == end.y && z == end.z) return dist[x][y][z];

            q[++tt] = { x, y, z };
        }
    }

    return -1;
}

int main()
{
    while (scanf("%d%d%d", &L, &R, &C), L || R || C)
    {
        Point start, end;
        for (int i = 0; i < L; i++)
            for (int j = 0; j < R; j++)
            {
                scanf("%s", g[i][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);
    }

    return 0;
}

 全球变暖

1233. 全球变暖 - AcWing题库

判断的时候有一点点不一样,只要上下左右有一个是海,那么它就会消失,所以是continue;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n;
char g[N][N];
bool st[N][N];
PII q[N * N];
int dx[4] = { -1,0,1,0 };
int dy[4] = { 0,1,0,-1 };
// total : 岛屿的板块的数量  bound:邻接海洋的岛屿板块的数量
void bfs(int sx, int sy, int& total, int& bound)
{
	int hh = 0;
	int tt = 0;
	q[0] = { sx,sy };
	st[sx][sy] = true;

	while (hh <= tt)
	{
		PII t = q[hh++];

		total++;
		bool is_bound = false;
		for (int i = 0; i < 4; i++)
		{
			int x = t.x + dx[i];
			int y = t.y + 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;
			}
			q[++tt] = { x,y };
			st[x][y] = true;
		}
		if (is_bound) bound++;//只要上下左右之中有一个为海,那么它就是边界
	}
}

int main()
{
	cin >> n;

	for (int i = 0; i < n; i++) cin >> g[i];

	int cnt = 0;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			if (!st[i][j] && g[i][j] == '#')
			{
				int total = 0;
				int bound = 0;
				bfs(i, j, total, bound);
				if (total == bound) cnt++;
			}
	cout << cnt << endl;

	return 0;
}

图论


交换瓶子

1224. 交换瓶子 - AcWing题库

暴力解法:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
int n;
const int N = 10010;
int a[N];
int res;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++)
    {
        if(a[i] != i)
        {
            for(int j = i + 1;j <= n;j++)
            {
                if(a[j] == i)
                {
                    int temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                }
            }
            res++;
        }
    }
    cout << res << endl;
}

核心:

这道题与逆序对不同的地方就在与它可以不相邻就进行交换

那么就有这样一个性质:交换次数=数组元素总个数-环个数(具体证明有链接:)

AcWing 1224. 交换瓶子 - AcWing

那么难点就来到了:如何判断有多少个环???

(1)连通块(用find函数寻找公共祖先)

如下代码所示:

# include<iostream>
# include<algorithm>

using namespace std;

const int N = 1e4+10;

int n, res;
int a[N], f[N], cnt[N];

int find(int x)
{
    if(x!=f[x])   return f[x] = find(f[x]);
    return f[x];
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i)   f[i] = i;             //初始化并查集中的数组f[]
    for(int i=1; i<=n; ++i)   cin>>a[i];            //读入数据

    for(int i=1; i<=n; ++i)
    {
        int fa = find(a[i]), fb = find(i);
        if(fa!=fb)   f[fa] = fb;                    //如果不在一个集合,就合并集合
    }

    for(int i=1; i<=n; ++i)             //判断回环的个数(并查集的大小)
    {
        int fa = find(a[i]);
        if(!cnt[fa])   res++;
        cnt[fa]++;
    }

    cout<<n-res<<endl;

    return 0;
}

(2)如下代码所示:

<1>如果访问的索引!=b[索引],那么就证明:这个索引和b[索引]必然在一个环中,重复上述过程,直到遇到一个索引==b[索引]的数,那么就证明这个数已经正确地站在了它应该在地位置,所以此时就跳出循环即可

巧妙遍历

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 10010;

int n;
int b[N];
bool st[N];

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> b[i];

	int cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		if (!st[i])
		{
			cnt++;//判环个数
			for (int j = i; !st[j]; j = b[j])
			{
				st[j] = true;
			}
		}
	}
	printf("%d\n", n - cnt);//次数=数组元素总个数-环个数

	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值