蓝桥杯C++ AB组辅导课题单:第六、七、八讲
一、双指针、BFS与图论
1238、日志统计(中等)(双指针)
说是双指针吧,我下面这个写出来好像也不是双指针,我是暴力枚举求出来的。
import java.util.*;
import java.io.*;
public class Main {
// 加速读取
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] input = in.readLine().trim().split(" ");
// 如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞
// 小明就认为这个帖子曾是”热帖”
// 所有曾是”热帖”的帖子编号。
// 在[T,T+D)区间内
int n = Integer.parseInt(input[0]);
int d = Integer.parseInt(input[1]);
int k = Integer.parseInt(input[2]);
ArrayList<Integer>[] idTime = new ArrayList[100001];
for (int i = 0; i < 100001; i++) {
idTime[i] = new ArrayList<>();
}
int[] ids = new int[n + 1];
for (int i = 0; i < n; i++) {
input = in.readLine().trim().split(" ");
int ts = Integer.parseInt(input[0]);
int id = Integer.parseInt(input[1]);
idTime[id].add(ts);
ids[i] = id;
}
Arrays.sort(ids);
for (int i = 0; i < n; i++) {
// 去重
if (i > 0 && ids[i] == ids[i - 1]) continue;
ArrayList<Integer> tmp = idTime[ids[i]];
// 时间从小到大
Collections.sort(tmp);
int j = 0;
int kk = 0;
int cnt = 0;
while (kk < tmp.size() && j <= kk) {
if (tmp.get(kk) - tmp.get(j) < d) {
cnt++;
if (cnt >= k) {
System.out.println(ids[i]);
break;
} else {
// 右移指针
kk++;
}
} else {
j++;
// 别忘了右指针也要拿回来!
kk = j;
cnt = 0;
}
}
}
}
}
下面这个才是真的双指针,并不是说你用了两个指针,一左一右跟着走就是双指针,具体要看你的指针是怎么移动的,如果还是照样暴力枚举,是没意义的。
import java.util.*;
import java.io.*;
class node {
int ts, id;
node() {};
node(int ts, int id) {
this.ts = ts;
this.id = id;
}
}
public class Main {
// 加速读取
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] input = in.readLine().trim().split(" ");
// 如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞
// 小明就认为这个帖子曾是”热帖”
// 所有曾是”热帖”的帖子编号。
// 在[T,T+D)区间内
int n = Integer.parseInt(input[0]);
int d = Integer.parseInt(input[1]);
int k = Integer.parseInt(input[2]);
node[] nodes = new node[n];
for (int i = 0; i < n; i++) {
input = in.readLine().trim().split(" ");
int ts = Integer.parseInt(input[0]);
int id = Integer.parseInt(input[1]);
nodes[i] = new node(ts, id);
}
// 按时间从小到大排序
Arrays.sort(nodes, new Comparator<node>() {
@Override
public int compare(node o1, node o2) {
return o1.ts - o2.ts;
}
});
// 双指针
int i = 0;
int j = 0;
// 记录点赞数
int[] cnt = new int[100001];
boolean[] is = new boolean[100001];
while (j < n && i <= j) {
int tid = nodes[j].id;
// 获得一个赞
cnt[tid]++;
while (nodes[j].ts - nodes[i].ts >= d) {
cnt[nodes[i].id]--;
i++;
}
if (cnt[tid] >= k) {
is[tid] = true;
}
j++;
}
for (int ii = 0; ii < 100001; ii++) {
if (is[ii]) System.out.println(ii);
}
}
}
还是双指针更快!
※1101、献给阿尔吉侬的花束(简单)(BFS最短路)
看到最短路BFS,没得跑。
import java.util.*;
import java.io.*;
class node {
int x, y;
node() {};
node(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
// 方向数组
static int[] x = new int[] {-1,1,0,0};
static int[] y = new int[] {0,0,-1,1};
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
int t = scan.nextInt();
char[][] map;
boolean[][] vis;
while ((t--) > 0) {
int r = scan.nextInt();
int c = scan.nextInt();
map = new char[r][c];
vis = new boolean[r][c];
Queue<node> queue = new LinkedList<>();
for (int i = 0; i < r; i++) {
map[i] = scan.next().toCharArray();
for (int j = 0; j < c; j++) {
if (map[i][j] == 'S') {
queue.offer(new node(i, j));
vis[i][j] = true;
}
}
}
boolean flag = false;
int time = 0;
// 最少时间BFS
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
node tmp = queue.poll();
for (int j = 0; j < 4; j++) {
int tx = tmp.x + x[j];
int ty = tmp.y + y[j];
if (tx < 0 || ty < 0 || tx >= r || ty >= c || vis[tx][ty] || map[tx][ty] == '#') continue;
// 找到终点提起结束
if (map[tx][ty] == 'E') {
flag = true;
break;
}
queue.offer(new node(tx, ty));
// 这里的vis标记不能少
vis[tx][ty] = true;
}
if (flag) break;
}
// 整体走完一轮
time++;
if (flag) break;
}
if (flag) System.out.println(time);
else System.out.println("oop!");
}
}
}
好久没做BFS,人给我写麻了。
※1113、红与黑(简单)(DFS求个数)
不用vis,直接把黑色换红色,记得每轮要重置计数器,还有就是不需要回溯,因为是统计全部黑色方块。而比如说求方案数的,则需要回溯。
import java.util.*;
import java.io.*;
class node {
int x, y;
node() {};
node(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
// 方向数组
static int[] x = new int[] {-1,1,0,0};
static int[] y = new int[] {0,0,-1,1};
static int cnt = 1;
static char[][] map;
static int w, h;
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
while (true) {
w = scan.nextInt();
h = scan.nextInt();
if (w == 0 && h == 0) break;
map = new char[h][w];
int bx = 0;
int by = 0;
for (int i = 0; i < h; i++) {
map[i] = scan.next().toCharArray();
for (int j = 0; j < w; j++) {
if (map[i][j] == '@') {
bx = i;by = j;
map[i][j] = '#';
}
}
}
// 更新cnt
cnt = 1;
dfs(bx, by);
System.out.println(cnt);
}
}
static void dfs(int i, int j) {
for (int k = 0; k < 4; k++) {
int tx = i + x[k];
int ty = j + y[k];
if (tx < 0 || ty < 0 || tx >= h || ty >= w || map[tx][ty] != '.') continue;
cnt++;
map[tx][ty] = '#';
dfs(tx, ty);
}
}
}
1224、交换瓶子(中等)(贪心)
第一次拿到这样的题真不知道该怎么做,想搜索不显示,数量太大了,但也能过一部分分数。第一个位置应该是1,如果不是1,我们假设它是4,并假设第四个位置是1,那就直接交换a[1]和a[a[1]],即交换一次,当然这是最普通情况,如果第四个位置不是1,那就还要继续交换,这个思维类似于贪心,我们保证每个位置必须交换为正确的数为止。
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] num = new int[n + 1];
for (int i = 1; i <= n; i++) {
num[i] = scan.nextInt();
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
while (num[i] != i) {
int tmp = num[i];
num[i] = num[tmp];
num[tmp] = tmp;
cnt++;
}
}
System.out.println(cnt);
}
}
※1240、完全二叉树的权值(简单)
这道题是二叉树搜索问题,待更,二叉树部分还要多摸摸~ (我太蠢了,这玩意根本用不到LeetCode那套东西…)
唯一要注意的是完全二叉树和满二叉树的区别,完全二叉树,是从上到下,从左到右依次构建节点,但是最后一层不一定是满的。而满二叉树是特殊的完全二叉树,它的每一层都是满的,包括最后一层,所以这道题很多人拿到80分的原因,就是因为当成了满二叉树,具体区别可以看下面这张图:
import java.util.*;
import java.io.*;
public class Main {
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
// 给的是层序遍历序列
// 完全二叉树(最后一行可能不是满的!) 不是 满二叉树!!!
int[] w = new int[n];
int index = 0;
for (int i = 0; i < n; i++) {
w[i] = scan.nextInt();
}
long[] weight = new long[n / 2];
index = 0;
long sum = 0L;
boolean flag = false;
for (int i = 1; i <= n; i++) {
sum += w[i - 1];
flag = false;
if (i == (int)Math.pow(2, index + 1) - 1) {
weight[index++] = sum;
sum = 0;
flag = true;
}
}
if (!flag) {
// 说明最后一层没有满!
weight[index++] = sum;
}
long max = weight[0];
int idx = 1;
for (int i = 1; i < index; i++) {
if (weight[i] > max) {
max = weight[i];
idx = i + 1;
}
}
System.out.println(idx);
}
}
1096、地牢大师(简单)(三维BFS)
标准的BFS最短路,只不过是三维的,题目中可以将map中的’.‘修改为’#’,从而实现vis数组标记的功能,当然也可以自己另外开一个数组记录。
import java.util.*;
import java.io.*;
class node {
int x, y, z;
node() {};
node(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}
public class Main {
static int[] x = new int[] {0,0,1,-1,0,0};
static int[] y = new int[] {1,-1,0,0,0,0};
static int[] z = new int[] {0,0,0,0,1,-1};
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
char[][][] map;
while (true) {
String[] str = in.readLine().trim().split(" ");
int l = Integer.parseInt(str[0]);
int r = Integer.parseInt(str[1]);
int c = Integer.parseInt(str[2]);
if (l == 0 && r == 0 && c == 0) break;
map = new char[l + 1][r + 1][c + 1];
Queue<node> queue = new LinkedList<>();
for (int i = 0; i < l; i++) {
for (int j = 0; j < r; j++) {
map[i][j] = in.readLine().trim().toCharArray();
for (int k = 0; k < c; k++) {
if (map[i][j][k] == 'S') {
queue.offer(new node(i, j, k));
map[i][j][k] = '#';
}
}
}
// 读走空行
in.readLine();
}
int time = 0;
boolean flag = false;
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
node tmp = queue.poll();
for (int j = 0; j < 6; j++) {
int tx = tmp.x + x[j];
int ty = tmp.y + y[j];
int tz = tmp.z + z[j];
if (tx < 0 || ty < 0 || tz < 0 || tx >= l || ty >= r || tz >= c) continue;
if (map[tx][ty][tz] == '#') continue;
if (map[tx][ty][tz] == 'E') {
flag = true;
break;
}
// 标记
map[tx][ty][tz] = '#';
queue.offer(new node(tx, ty, tz));
}
if (flag) break;
}
// 完成一轮
time++;
if (flag) break;
}
if (flag) System.out.printf("Escaped in %d minute(s).\n", time);
else System.out.println("Trapped!");
}
}
}
※1233、全球变暖(简单)(DFS找:边点)
乍一看好像不咋好做,我们但看一个岛屿,如果它的邻海的边的像素个数 = 整体的像素个数,那这个岛屿必被淹没。而一个个岛屿必须是有海面隔开的,所以我们遍历每个#点,去找当前岛屿领海的边的像素个数,与整体的像素个数,比较两者即可得到答案。
import java.util.*;
import java.io.*;
public class Main {
static int n;
static char[][] map;
static boolean[][] vis;
// 记录边像素点和整体像素点
static int around, total;
// 方向数组
static int[] x = new int[] {0,0,1,-1};
static int[] y = new int[] {1,-1,0,0};
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] str = in.readLine().trim().split(" ");
n = Integer.parseInt(str[0]);
map = new char[n][n];
for (int i = 0; i < n; i++) {
map[i] = in.readLine().toCharArray();
}
vis = new boolean[n][n];
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (map[i][j] == '#' && vis[i][j] == false) {
// 能够通过这里循环遍历到的#点,一定是边点
// 边点初始化为0,因为进去之后dfs会统计
around = 0;
// 整体点数初始化为0,dfs进去不做统计
total = 1;
vis[i][j] = true;
dfs(i, j);
// 有岛屿被淹没
if (around == total) ans++;
}
}
}
System.out.println(ans);
}
static void dfs(int i, int j) {
// 先找边数
boolean flag = false;
for (int k = 0; k < 4; k++) {
int tx = i + x[k];
int ty = j + y[k];
if (tx < 0 || ty < 0 || tx >=n || ty >= n || map[tx][ty] == '.') {
flag = true;
break;
}
}
// 说明当前点是边点
if (flag) {
around++;
}
for (int k = 0; k < 4; k++) {
int tx = i + x[k];
int ty = j + y[k];
if (tx < 0 || ty < 0 || tx >=n || ty >= n || map[tx][ty] == '.' || vis[tx][ty]) continue;
total++;
vis[tx][ty] = true;
dfs(tx, ty);
}
}
}
※1207、大臣的旅费(中等)(树的直径)
从首都到达每个大城市的方案是唯一的,也就是一棵树,本题也就是在找一棵树的直径,也就是树中长度最长的路径,寻找方法:任取一点x,找到距离x最远的点y,从y开始遍历,找到距离y最远的点,该点与y的距离就是树的直径。
本题的路径长度是行走的花费,就是一个等差数列求和。
(11+(10 + d))* d /2
这道题Acwing的最后一个样例格式有问题,蓝桥杯系统是Ac的。
import java.util.*;
import java.io.*;
public class Main {
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(in.readLine().trim().split(" ")[0]);
// 任何一个大城市都能从首都直接或者通过其他大城市间接到达
// 建图
List<int[]>[] graph = new LinkedList[n + 1];
for (int i = 0; i < n + 1; i++) {
graph[i] = new LinkedList<>();
}
String[] str;
for (int i = 0; i < n - 1; i++) {
str = in.readLine().trim().split(" ");
int p = Integer.parseInt(str[0]);
int q = Integer.parseInt(str[1]);
int d = Integer.parseInt(str[2]);
graph[p].add(new int[] {q, d});
graph[q].add(new int[] {p, d});
}
Queue<Integer> queue = new LinkedList<>();
// 先找距离某点(例如:起点)最远的点
queue.offer(1);
// 记录距离起点的所有点的距离
int[] dist = new int[n + 1];
dist[1] = 0;
boolean[] vis = new boolean[n + 1];
vis[1] = true;
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
int cur = queue.poll();
for(int[] next : graph[cur]) {
int id = next[0];
int weight = next[1];
if (vis[id]) continue;
dist[id] = dist[cur] + weight;
vis[id] = true;
queue.offer(id);
}
}
}
// 找距离最远的节点
int index = 0;
long max = 0;
for (int i = 1; i <= n; i++) {
if (dist[i] > max) {
max = dist[i];
index = i;
}
}
// 再以这个节点为起点,找最远的距离
dist = new int[n + 1];
queue = new LinkedList<>();
vis = new boolean[n + 1];
queue.offer(index);
dist[index] = 0;
vis[index] = true;
while (!queue.isEmpty()) {
int sz = queue.size();
for (int i = 0; i < sz; i++) {
int cur = queue.poll();
for (int[] next : graph[cur]) {
if (vis[next[0]]) continue;
dist[next[0]] = dist[cur] + next[1];
vis[next[0]] = true;
queue.offer(next[0]);
}
}
}
// 找最大距离
max = 0;
for (int i = 1; i <= n; i++) {
max = Math.max(max, dist[i]);
}
System.out.println(1L * (11 + 10 + max) * max / 2);
}
}
※826、单链表(简单)(数组模拟链表)
用数组来模拟链表,注意代码中idx的妙用!
import java.util.*;
import java.io.*;
public class Main {
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static int N = 100010;
static int head;
// 当前节点的值
static int[] val = new int[N];
// 当前节点的下一个节点
static int[] next = new int[N];
// 记录整体节点数
static int idx;
// 初始化数组链表
static void build() {
head = -1;
idx = 0;
}
// 链表头插入节点
static void insertHead(int v) {
val[idx] = v;
// 同时让最后一个节点的下一个节点标记为-1
next[idx] = head;
head = idx;
idx++;
}
// 将下标k的点后面的点删除(实际是下标的跳转修改)
static void deleteK(int k) {
next[k] = next[next[k]];
}
// 在下标为k的后面插入val
static void insertK(int k, int v) {
val[idx] = v;
next[idx] = next[k];
next[k] = idx;
idx++;
}
public static void main(String[] args) throws IOException {
int m = Integer.parseInt(in.readLine().trim().split(" ")[0]);
String[] str;
// 初始化数组链表
build();
while ((m--) > 0) {
str = in.readLine().trim().split(" ");
if (str[0].equals("H")) {
insertHead(Integer.parseInt(str[1]));
} else if (str[0].equals("I")) {
insertK(Integer.parseInt(str[1]) - 1, Integer.parseInt(str[2]));
} else {
int k = Integer.parseInt(str[1]);
if (k == 0) {
// 删除头节点
head = next[head];
} else {
deleteK(k - 1);
}
}
}
for (int i = head; i != -1; i = next[i]) {
System.out.printf("%d ", val[i]);
}
}
}
二、贪心
1055、股票买卖Ⅱ
104、货仓选址
122、糖果传递
112、雷达设备
1235、付账问题
1239、乘积最大
1247、后缀表达式
1248、灵能传输
贪心问题并不存在特定规律,每道题都有自己的贪心方法,不具备归纳性,大伙看着刷刷就行,对于能力薄弱的同学,更应该注重于模板题。
三、数论
这一讲更加玄学,因为考察的就是数学知识,如果没有遇见过,只能靠暴力骗分。
1246、等差数列(简单)
首先要找出等差数列项数的公式,分析变量,再去最大化项数,也就是最大化公差d,最后转化为项数差值的GCD,当然你也可以暴力,能拿70 80分,也不错了。
import java.util.*;
import java.io.*;
public class Main {
static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scan.nextInt();
}
Arrays.sort(nums);
int max = 0;
for (int i = 1; i < n; i++) {
max = gcd(max, nums[i] - nums[i - 1]);
}
if (max == 0) {
// 说明公差=0
System.out.println(n);
} else {
System.out.println((nums[n - 1] - nums[0]) / max + 1);
}
}
}
1295、X的因子链(中等)
这道题太复杂了,能够掌握线性筛求质数即可。
public class Main {
public static void main(String[] args) {
int n = 1000;
int[] prime = new int[n + 1];
boolean[] isPrime = new boolean[n + 1];
int idx = 0;
for (int i = 2; i < n; i++) {
if (isPrime[i] == false) {
prime[idx++] = i;
System.out.println(i);
}
for (int j = 0; j < idx; j++) {
if (prime[j] * i > n) break;
isPrime[prime[j] * i] = true;
if (i % prime[j] == 0) break;
}
}
}
}
1296、聪明的燕姿(中等)
这题一样较为复杂,只需要掌握约束个数和约束之和的算法即可:
1299、五指山(简单)
先暴力,骗一点是一点~
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int t = scan.nextInt();
while (t-- > 0) {
int n = scan.nextInt();
int d = scan.nextInt();
int x = scan.nextInt();
int y = scan.nextInt();
int cnt = 0;
boolean[] vis = new boolean[n];
vis[x] = true;
boolean flag = false;
while (x != y) {
x = (x + d) % n;
if (vis[x]) {
flag = true;
break;
}
vis[x] = true;
cnt++;
}
if (flag) {
System.out.println("Impossible");
} else {
System.out.println(cnt);
}
}
}
}
下面给出正解:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
while(T-- > 0) {
long n = sc.nextLong();
long d = sc.nextLong();
long x = sc.nextLong();
long y = sc.nextLong();
Long a = new Long();
Long b = new Long();
long gcd = exgcd(n, d, a, b);
if((y - x) % gcd != 0) {
System.out.println("Impossible");
}else {
b.v *= (y - x) / gcd;
n /= gcd;
System.out.println((b.v % n + n) % n);
}
}
sc.close();
}
private static long exgcd(long a, long b, Long x, Long y) {
if(b == 0) {
x.v = 1;
y.v = 0;
return a;
}
long d = exgcd(b, a % b, y, x);
y.v -= a / b * x.v;
return d;
}
}
class Long{
long v;
public Long() {};
public Long(long v) {
this.v=v;
}
}
1223、最大比例
1301、C循环
1225、正则问题
1243、糖果
贪心和数论一直都是算法题目中的难题,对于想要短时间取得好成绩的同学,一定多花时间在模板题上,因为在赛场上真正能静下来把贪心、数论题目做清楚,做明白的,真的很少。