双指针、BFS与图论

1.日志统计

ACWing1238. 日志统计

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:

ts id  

表示在 ts 时刻编号 id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [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<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 100010;
int n, d,k;
PII logs[N];//记录时间与id 
int cnt[N];//记录id出现次数
bool st[N];//记录每个帖子是否是热帖
int main()
{
    scanf("%d%d%d", &n, &d, &k);
    for(int i = 0; i < n; i++) scanf("%d%d", &logs[i].x, &logs[i].y);
    
    sort(logs,logs + n);//按照时间排序
    for(int i = 0, j = 0; i < n; i++)
    {
        int id = logs[i].y;//新加帖子的id
        cnt[id]++;
        while(logs[i].x - logs[j].x >= d) //时间跨度
        {
            cnt[logs[j].y]--;
            j++;
        }
        
        if(cnt[id] >= k) st[id] = true;//如果当前这个id的赞大于等于k,标记为是热帖
    }
    
    for(int i = 0;i <= 100000;i++)//遍历id
     if(st[i])
      printf("%d\n", i);
      
      return 0;
}

2.献给阿尔吉侬的花束

AcWing1101. 献给阿尔吉侬的花束
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 210;
int n, m;
char g[N][N];
int d[N][N];
int bfs(PII start, PII end)
{
    queue<PII> q;//队列
    memset(d, -1, sizeof d);//将距离初始化为-1
    d[start.x][start.y] = 0;//起点的距离为0
    q.push(start);//将起点放到队列中
    
    int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1};
    while(q.size())
    {
        auto t = q.front();//队头
        q.pop();//队头元素出队
        for(int i = 0; i < 4; i++)//遍历四个方向
        {
            int x = t.x + dx[i], y = t.y + dy[i];
            if(x < 0 || x >= n || y <0 || y >= m || g[x][y] == '#' || d[x][y] != -1) continue;//出界 或者不能走 或者 已走过
            
            //如果可以走,更新距离
            d[x][y] = d[t.x][t.y] + 1;
            if(end == make_pair(x, y)) return d[x][y];//返回到终点的距离
            q.push({x, y});
        }
    }
    return -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]);
        
        PII 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);
        if(distance == -1) puts("oop!");
        else printf("%d\n",distance );
        
    }
    return 0;
}

3.红与黑

AcWing1113. 红与黑
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。

你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。

请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式
输入包括多个数据集合。

每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。

在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下

1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

数据范围
1≤W,H≤20
输入样例:

6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0

输出样例:

45
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 30; 
int w, h;
char g[N][N];
bool st[N][N];
int ans;
int dfs(int x, int y)
{
    int cnt = 1;   
    int dx[4] = {-1, 0, 1,0}, dy[4] = {0,1,0,-1};
    st[x][y] = true;
    for(int i = 0; i < 4; i++)
    {
        int a = x + dx[i], b = y +dy[i];
        if(a >= 0 && b >= 0 && a < h && b < w && g[a][b] == '.' && !st[a][b]) cnt += dfs(a, b);//不需要回溯
    }
    return cnt;
}
int main()
{
   
    int x, y;
   while(cin >> w >> h && w || h)//输入判断
   {
       for(int i = 0;i < h; i++) cin >> g[i];
       
       for(int i = 0; i < h ; i++)
        for(int j = 0; j <w; j++)
            if(g[i][j] == '@')//找起点
            {
                x = i; 
                y = j;
            }
        
        memset(st, 0, sizeof st);
        cout << dfs(x, y)<< endl;
   }
   
     return 0;
}

4.交换瓶子

AcWing1224. 交换瓶子
有 N 个瓶子,编号 1∼N,放在架子上。

比如有 5 个瓶子:

2 1 3 5 4

要求每次拿起 2 个瓶子,交换它们的位置。

经过若干次后,使得瓶子的序号为:

1 2 3 4 5

对于这么简单的情况,显然,至少需要交换 2 次就可以复位。

如果瓶子更多呢?你可以通过编程来解决。

输入格式
第一行包含一个整数 N,表示瓶子数量。

第二行包含 N 个整数,表示瓶子目前的排列状况。

输出格式
输出一个正整数,表示至少交换多少次,才能完成排序。

数据范围
1≤N≤10000,

输入样例1:

5
3 1 2 5 4

输出样例1:

3

输入样例2:

5
5 4 3 2 1

输出样例2:

2

将瓶子与对应的位置连接起来
在这里插入图片描述

  1. 情况1:交换同一个环内的点——>裂成两个环
  2. 情况2:交换不同环中的点——>合并两个环
#include<algorithm>
#include<cstring>
#include<cstdio>
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++)  scanf("%d", &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;
}

5. 完全二叉树的权值

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<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, a[N];
int main()
{
	cin >> n;
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	LL maxs = -1e18;
	int depth = 0;
	for(int d = 1, i = 1; i <= n; i*=2, d++)//循坏遍历每层,d表示层数,i表示每层第一个节点的下标 
	{
		LL s = 0;
		for(int j = i; j < i + (1<<d -1) && j <= n ; j++)//循环每层,求和,找规律 
		s += a[j];
		if(s >maxs)
		{
			maxs = s;
			depth = d;
		}
	}
	printf("%d\n", depth);
	return 0; 
 } 

6.地牢大师

AcWing1096. 地牢大师
你现在被困在一个三维地牢中,需要找到最快脱离的出路!

地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。

向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。

你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。

请问,你有可能逃脱吗?

如果可以,需要多长时间?

输入格式
输入包含多组测试数据。

每组数据第一行包含三个整数 L,R,C 分别表示地牢层数,以及每一层地牢的行数和列数。

接下来是 L 个 R 行 C 列的字符矩阵,用来表示每一层地牢的具体状况。

每个字符用来描述一个地牢单元的具体状况。

其中, 充满岩石障碍的单元格用”#”表示,不含障碍的空单元格用”.”表示,你的起始位置用”S”表示,终点用”E”表示。

每一个字符矩阵后面都会包含一个空行。

当输入一行为”0 0 0”时,表示输入终止。

输出格式
每组数据输出一个结果,每个结果占一行。

如果能够逃脱地牢,则输出”Escaped in x minute(s).”,其中X为逃脱所需最短时间。

如果不能逃脱地牢,则输出”Trapped!”。

数据范围
1≤L,R,C≤100
输入样例:

3 4 5
S....
.###.
.##..
###.#

#####
#####
##.##
##...

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

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

0 0 0

输出样例:

Escaped in 11 minute(s).
Trapped!
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
struct Point
{
    int x,y,z;
};

const int N = 110;
int L, R, C;
char g[N][N][N];
Point q[N*N*N];
int d[N][N][N];
int dx[6] = {0, 1, 0, -1, 0, 0}; //东南西北上下
int dy[6] = {1, 0, -1, 0, 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(d, -1,sizeof d);
    d[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(d[x][y][z] != -1) continue;

            d[x][y][z] = d[t.x][t.y][t.z] + 1;
            if(x == end.x && y == end.y && z == end.z)  return d[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) printf("Trapped!\n");
          else printf("Escaped in %d minute(s).\n", distance);
    }
    return 0;
}

7.全球变暖

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

.......
.##....
.##....
....##.
..####.
...###.
.......

其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 2 座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。

具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

输入格式
第一行包含一个整数N。

以下 N 行 N 列,包含一个由字符”#”和”.”构成的 N×N 字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。

照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。

输出格式
一个整数表示答案。

数据范围
1≤N≤1000
输入样例1:

7
.......
.##....
.##....
....##.
..####.
...###.
.......

输出样例1:

1

输入样例2:

9
.........
.##.##...
.#####...
.##.##...
.........
.##.#....
.#.###...
.#..#....
.........

输出样例2:

1

算法分析
遍历所有未遍历过的陆地,通过bfs计算出当前位置连通陆地的数量total,以及被淹没陆地的数量bound,若total == bound表示完整淹没的一个岛屿

  • 1.算出有多少个连通块
  • 2.多少个会被淹没 一共有多少个岛屿连通,边界与海相连的会被淹没
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#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}, dy[4] = {0,1,0,-1}; 
void bfs(int x, int y, int &total, int &bound)
{
    int hh = 0, tt = 0;
    q[0] = {x, y};
    st[x][y] = true;
    while(hh <= tt)
    {
        PII t = q[hh++];
        total ++;
        bool is_bound = false;
        for(int i = 0; i < 4; i++)
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if(a < 0 || b < 0|| a >= n || b >= n) continue;//出界
            if(st[a][b]) continue;
            if(g[a][b]  == '.')//如果是大海
            {
                is_bound = true;//这个块的与海相连
                continue;
            }
            q[++tt] = {a, b};
            st[a][b] = true;
            
        }
        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(!st[i][j] && g[i][j] == '#')//如果当前这个格子没有被搜过并且是岛屿
      {
          int total = 0, bound = 0;//所有单元和边界的数量
          bfs(i, j, total, bound);
          if(total == bound) cnt++;//如果所有单元都在边界上,被淹没的岛屿数量++
      }
      printf("%d\n", cnt);
      return 0;
}

8.大臣的旅费

AcWing1207. 大臣的旅费
很久以前,T王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式
输入的第一行包含一个整数 n,表示包括首都在内的T王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述T国的高速路(T国的高速路一定是 n−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

分析:
由于题目说到不重复经过大城市,从首都到达每个大城市的方案都是唯一的。因此可以知道该图是一棵树,本题求的是树的直径

树的直径:树中长度最长的路径

  • 1、任取一点x

  • 2、找到距离x最远的点y

  • 3、从y开始遍历,找到离y最远的点,与y最远的点的距离是树的直径

dfs

1、通过深度优先遍历找到与x的最远距离的点y

2、再通过深度优先遍历找到与y的最远距离

注意:递归函数需要记录上一结点father

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 100010;
int n;
int d[N];
struct Edge
{
    int id, w;
};
vector<Edge> h[N];

void dfs(int u, int father, int dist)
{
    d[u] = dist;
    for(auto node : h[u])//遍历当前节点的所有连接点
    if(node.id != father) //不往回走的话,继续遍历
    {
        dfs(node.id, u, dist + node.w);
    }
}
int main()
{
    cin >> n ;
    memset(d,-1,sizeof d);
    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);
    
    //找到距离1号点最远的一个点
    int u = 1;
    for(int i = 1; i <= n; i++)
    if(d[i] > d[u]) u = i;
    
    dfs(u, -1, 0);
    
    for(int i = 1; i <= n; i++)
    if(d[i] >d[u]) u  = i;
    
    int s = d[u];
    printf("%lld\n", s*10 + s*(s +1ll)/2);//费用的公式
    
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值