目录
双指针
日志统计(双指针+滑动窗口)
题目概述:输入日志的编号和点赞时刻,求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;
}
完全二叉树的权值
传统的寻找最值的做法:
(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)跳出循环,队列为空,那么就证明了,从起始点无法到达终点,此时输出对应的语句
献给阿尔吉侬的花束
一点点不一样:
输入好起点之后,并用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;
}
红与黑
没啥可讲,纯模板
#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;
}
地牢大师
三维地图而已,只是多加了一个维度,和多判断条件而已
#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;
}
全球变暖
判断的时候有一点点不一样,只要上下左右有一个是海,那么它就会消失,所以是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;
}
图论
交换瓶子
暴力解法:
#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;
}
核心:
这道题与逆序对不同的地方就在与它可以不相邻就进行交换
那么就有这样一个性质:交换次数=数组元素总个数-环个数(具体证明有链接:)
那么难点就来到了:如何判断有多少个环???
(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;
}