双指针,BFS,图论
双指针
日志统计
小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 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<algorithm>
#include<cmath>
#define x first
#define y second
using namespace std;
typedef pair<int ,int> PII;
const int N=1e5+10;
PII a[N];
bool st[N];
int cnt[N];
int main()
{
int k,n,d;
cin>>n>>d>>k;
for(int i=0;i<n;i++) cin>>a[i].x>>a[i].y;
sort(a,a+n);
for(int i=0,j=0;i<n;i++)
{
int id=a[i].y;
cnt[id]++;
// return 0;
while(a[i].x-a[j].x>=d)
{
cnt[a[j].y]--;
j++;
}
if(cnt[id]>=k) st[id]=true; //标记热帖
}
for(int i=0;i<=100000;i++)
if(st[i]) cout<<i<<endl;
}
完全二叉树的权值
给定一棵包含 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<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100010;
int n;
int a[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
LL maxs=-1e18;
int depth=0;
for(int i=1,d=1;i<=n;i=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",depth);
}
BFS(求最短距离)
红与黑
洪水灌溉算法——网格图——过程
1)取出队头t
2)枚举t的四个相邻格子,满足条件,标记并插入队列
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。
你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。
请写一个程序,计算你总共能够到达多少块黑色的瓷砖。
输入格式
输入包括多个数据集合。每个数据集合的第一行是两个整数 W和 H,分别表示 x
方向和 y方向瓷砖的数量。
在接下来的 H 行中,每行包括 W个字符。每个字符表示一块瓷砖的颜色,规则如下
1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。
当在一行中读入的是两个零时,表示输入结束。
输出格式
对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。
数据范围
1≤W,H≤20
输入样例:
6 9
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
0 0
输出样例:
45
(1)BFS
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int ,int> PII;
const int N=25;
char g[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
int n,m;
int bfs(int x,int y)
{
queue <PII> q;
q.push({x,y});
g[x][y]='#'; //标记为障碍物
int res=0;
while(q.size()) //当队列不空
{
PII t=q.front();
q.pop();
res++;
for(int i=0;i<4;i++)
{
int sx=t.x+dx[i],sy=t.y+dy[i];
if(sx<0||sx>=n||sy<0||sy>=m||g[sx][sy]!='.') continue;
g[sx][sy]='#';
q.push({sx,sy});
}
}
return res;
}
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;
}
cout<<bfs(x,y)<<endl;
}
}
(2)DFS
#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 dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
int dfs(int x,int y)
{
int res=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==h||b<0||b==w) continue;
if(st[a][b]) continue;
if(g[a][b]=='#') continue;
res+=dfs(a,b);
}
return res;
}
int main()
{
while(cin>>w>>h,h||w)
{
int x,y;
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;
break;
}
}
}
memset(st,0,sizeof st);
cout<<dfs(x,y)<<endl;
}
}
献给阿尔吉侬的花束BFS(求最短距离)
阿尔吉侬是一只聪明又慵懒的小白鼠,它最擅长的就是走各种各样的迷宫。今天它要挑战一个非常大的迷宫,研究员们为了鼓励阿尔吉侬尽快到达终点,就在终点放了一块阿尔吉侬最喜欢的奶酪。现在研究员们想知道,如果阿尔吉侬足够聪明,它最少需要多少时间就能吃到奶酪。迷宫用一个 R×C 的字符矩阵来表示。
字符 S 表示阿尔吉侬所在的位置,字符 E 表示奶酪所在的位置,字符 # 表示墙壁,字符 . 表示可以通行。阿尔吉侬在 1 个单位时间内可以从当前的位置走到它上下左右四个方向上的任意一个位置,但不能走出地图边界。
输入格式
第一行是一个正整数 T,表示一共有 T组数据。
每一组数据的第一行包含了两个用空格分开的正整数 R 和 C,表示地图是一个 R×C 的矩阵。
接下来的 R 行描述了地图的具体内容,每一行包含了 C个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 E。
输出格式
对于每一组数据,输出阿尔吉侬吃到奶酪的最少单位时间。
若阿尔吉侬无法吃到奶酪,则输出“oop!”(只输出引号里面的内容,不输出引号)。每组数据的输出结果占一行。
数据范围
1<T≤10,
2≤R,C≤200
输入样例:
3
3 4
.S..
###.
..E.
3 4
.S..
.E..
....
3 4
.S..
####
..E.
输出样例:
5
1
oop!
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define x first
#define y second
using namespace std;
const int N=210;
typedef pair<int,int>PII;
int n,m,t;
char g[N][N];
int d[N][N]; //距离
int bfs(PII start)
{
queue<PII> q;
q.push(start);
memset(d,-1,sizeof d);
d[start.x][start.y]=0;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
while(q.size())
{
PII 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)
{
d[x][y]=d[t.x][t.y]+1;
if(g[x][y]=='E')
return d[x][y];
q.push({x,y});
}
}
}
return -1;
}
int main()
{
cin>>t;
PII start;
while(t--)
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>g[i];
for(int j=0;j<m;j++)
if(g[i][j]=='S') start={i,j};
}
int res=bfs(start);
if(res==-1) puts("oop!");
else cout<<res<<endl;
}
}
地牢大师
三维
你现在被困在一个三维地牢中,需要找到最快脱离的出路!
地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。
向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。
你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。
请问,你有可能逃脱吗?
如果可以,需要多长时间?
输入格式
输入包含多组测试数据。
每组数据第一行包含三个整数 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<queue>
using namespace std;
struct point{
int x,y,z;
};
int l,r,c;
const int N=110;
char g[N][N][N];
point q[N*N*N]; //定义队列
int dis[N][N][N];
int dx[6]={1,0,0,0,-1,0},dy[6]={0,0,0,1,0,-1},dz[6]={0,-1,1,0,0,0};
int bfs(point start,point end)
{
int hh=0,tt=0;
q[0]=start;
memset(dis,-1,sizeof dis);
dis[start.x][start.y][start.z]=0;
while(hh<=tt)
{
point t=q[hh++];
for(int i=0;i<6;i++)
{
int x=t.x+dx[i];
int y=t.y+dy[i];
int 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(dis[x][y][z]!=-1) continue; //已经走过
dis[x][y][z]=dis[t.x][t.y][t.z]+1;
if(x==end.x&&y==end.y&&z==end.z) return dis[x][y][z];
q[++tt]={x,y,z};
}
}
return -1;
}
int main()
{
while(cin>>l>>r>>c,c||l||r)
{
for(int i=0;i<l;i++)
for(int j=0;j<r;j++)
cin>>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++)
{
if(g[i][j][k]=='S')
{
start.x=i;
start.y=j;
start.z=k;
}
if(g[i][j][k]=='E')
{
end.x=i;
end.y=j;
end.z=k;
}
}
}
}
int res=bfs(start,end);
if(res!=-1) printf("Escaped in %d minute(s).\n",res);
else printf("Trapped!\n");
}
}
全球气候变暖
你有一张某海域 N×N 像素的照片,”.”表示海洋、”#”表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 2 座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入格式
第一行包含一个整数N。以下 N 行 N 列,包含一个由字符”#”和”.”构成的 N×N
字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。
照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。
输出格式
一个整数表示答案。
数据范围
1≤N≤1000
输入样例1:
7
.......
.##....
.##....
....##.
..####.
...###.
.......
输出样例1:
1
输入样例2:
9
.........
.##.##...
.#####...
.##.##...
.........
.##.#....
.#.###...
.#..#....
.........
输出样例2:
1
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define x first
#define y second
using namespace std;
typedef pair<int ,int> PII;
const int N=1010;
char g[N][N];
bool st[N][N];
int cnt;
PII q[N*N];
int n;
int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int bfs(int sx, int sy,int &bound,int &sum)
{
int hh=0,tt=0;
q[0]={sx,sy};
st[sx][sy]=1; // 开始标记
while(hh<=tt)
{
PII t=q[hh++];
int flag=0;
sum++; // 开始加一
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]=='.')
{
flag=1; //边界
continue;
}
st[x][y]=1;
q[++tt]={x,y};
}
if(flag==1) bound++;
}
}
int main()
{
cin>>n;
for(int i=0;i<n;i++) scanf("%s",&g[i]);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(g[i][j]=='#'&&st[i][j]!=1)
{
int bound=0,sum=0;
bfs(i,j,bound,sum);
if(bound==sum) cnt++;
}
}
cout<<cnt;
}
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<int,int>PII;
const int N=1010;
char g[N][N];
bool st[N][N];
int n;
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
void bfs(int x,int y,int &sum,int &bound)
{
queue<PII>q;
q.push({x,y});
st[x][y]=1;
while(q.size())
{
PII t=q.front();
q.pop();
bool flag=0;
sum++; //第一个
for(int i=0;i<4;i++)
{
int sx=dx[i]+t.x,sy=dy[i]+t.y;
if(sx>=0&&sy>=0&&sx<n&&sy<n&&!st[sx][sy])
{
if(g[sx][sy]=='#')
{
q.push({sx,sy});
st[sx][sy]=1; //标记遍历过
}
if(g[sx][sy]=='.') flag=1;
}
}
if(flag) bound++;
}
}
int main()
{
cin>>n;
int cnt=0;
for(int i=0;i<n;i++) cin>>g[i];
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;
// return 0;
bfs(i,j,sum,bound);
// cout<<sum<<" "<<bound<<endl;
if(sum==bound) cnt++;
}
}
cout<<cnt<<endl;
return 0;
}
图论
树的重心
深度优先遍历
给定一棵树,树中包含n个节点和n-1条无向边
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心是指树中的一个节点,如果将这个点删除后,剩余各个连通块中点数的最大值最小
输入格式
第一行包含整数n,表示树的节点数
接下来n-1行,每行包含两个整数a和b,表示点a和点b之间存在一条边
输出格式
输出一个整数m,表示重心的所有子树中最大的子树的节点数目
样例输入
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出
4
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010,M=N*2;
int h[N],e[M],ne[M],idx;
bool st[N];
int ans=N,n,m;
void add(int a,int b ) //a指向b
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
//返回以u为根的子树中点的数量
int dfs(int u)
{
st[u]=true;//已经搜过
int sum=1,res=0;
for(int i=h[u];i!=-1;i=ne[i])//遍历u的所有初边
{
int j=e[i]; //当前节点对应图里的编号
if(!st[j])
{
int s= dfs(j);
res=max(res,s);
sum+=s;
}
}
res=max(res,n-sum);
// cout<<u<<" "<<sum<<" "<<ans<<endl;
ans=min(ans,res);
return sum;
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
dfs(1);
cout<<ans;
return 0;
}
树的宽度优先遍历
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int h[N],e[N],ne[N],idx;
int d[N],q[N],n,m;
void add(int a,int b)
{
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int bfs()
{
int hh=0,tt=0;
q[0]=1;
memset(d,-1,sizeof d);
d[1]=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]==-1)
{
d[j]=d[t]+1;
q[++tt]=j;
}
}
}
return d[n];
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
add(a,b);
}
cout<<bfs()<<endl;
return 0;
}
交换瓶子
情况1: 交换同一个环内的点,必然裂为两个环
情况2: 交换不同环内的点,必然合并为一个环
有 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
分析
(i) 位置 : 1 2 3 4 5
(b[i]) 瓶子 : 3 1 2 5 4
有cnt个环,要拆成n个独立的环,至少需要n-cnt次操作
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
int b[N];
bool st[N];
int main()
{
int n;
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;
}
cout<<n-cnt;
}
大臣的旅费(求树的直径)
方法
任取一点x, 求所有点到x的距离
找到距离x最远的点y
求所有点到y的距离,最大值即为直径
很久以前,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
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=100010;
int n;
struct edge{
int id,w;
};
vector<edge> h[N];
int dist[N];
void dfs(int u,int father,int distance)
{
dist[u]=distance;
for(auto node:h[u])
if(node.id!=father)
dfs(node.id,u,distance+node.w);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n-1;i++) //n个点,n-1条边
{
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); //编号,从哪个点过来的,
int u=1;
for(int i=1;i<=n;i++)
{
if(dist[i]>dist[u]) u=i;
}
dfs(u,-1,0);
for(int i=1;i<=n;i++)
if(dist[i]>dist[u]) u=i;
int s=dist[u];
printf("%lld",s*10+s*(s+1ll)/2);
}