题目帖:https://bbs.csdn.net/topics/606752897
图论+数学
🥩 扫雷游戏(简单)
签到题,千万别每一个格子遍历八个方向,遍历地雷的八个方向就行。
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] input = reader.readLine().trim().split(" ");
int n = Integer.parseInt(input[0]);
int m = Integer.parseInt(input[1]);
char[][] map = new char[n][m];
for (int i = 0; i < n; i++) {
map[i] = reader.readLine().trim().toCharArray();
}
int[] x = new int[] {0,0,1,-1,-1,1,-1,1};
int[] y = new int[] {1,-1,0,0,1,1,-1,-1};
// 记录答案
int[][] ans = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
// 如果当前位置是地雷,那就遍历它周围的八个方向
if (map[i][j] == '*') {
ans[i][j] = -1;
for (int k = 0; k < 8; k++) {
int tx = i + x[k];
int ty = j + y[k];
if (tx < 0 || ty < 0 || tx >= n || ty >= m) continue;
if (map[tx][ty] == '?') {
ans[tx][ty]++;
}
}
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (ans[i][j] == -1) {
writer.write("*");
} else {
writer.write(ans[i][j] + "");
}
}
writer.write("\n");
}
writer.flush();
}
}
🥞 排序(简单)
🍺 通信线路(中等)
巧妙点在于:二分答案!
import java.util.*;
import java.io.*;
import java.util.List;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] input = reader.readLine().trim().split(" ");
int n = Integer.parseInt(input[0]);
int p = Integer.parseInt(input[1]);
int k = Integer.parseInt(input[2]);
List<int[]>[] graph = new LinkedList[n + 1];
for (int i = 0; i < n + 1; i++) graph[i] = new LinkedList<>();
while (p-- > 0) {
input = reader.readLine().trim().split(" ");
int a = Integer.parseInt(input[0]);
int b = Integer.parseInt(input[1]);
int l = Integer.parseInt(input[2]);
// 双向电缆
graph[a].add(new int[] {b, l});
graph[b].add(new int[] {a, l});
}
// 二分答案
int left = 0;
int right = 1000001;
int mid = 0;
int ans = -1;
// 注意二分答案的模板,left、right都是mid+/-1,while判断条件要取等!
while (left <= right) {
mid = left + (right - left) / 2;
if (spfa(mid, graph, k)) {
ans = mid;
right = mid - 1; // 尝试尽量再次缩小最大值
} else {
left = mid + 1;
}
}
System.out.println(ans);
}
static boolean spfa(int mid, List<int[]>[] graph, int k) {
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
boolean[] vis = new boolean[graph.length];
vis[1] = true;
int[] dist = new int[graph.length];
Arrays.fill(dist, 0x3f3f3f3f);
dist[1] = 0;
while (!queue.isEmpty()) {
int u = queue.poll();
vis[u] = false;
for (int[] next : graph[u]) {
int v = next[0];
int w = next[1];
// 用新权值替代原来的权值
w = w > mid ? 1 : 0;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (vis[v] == false) {
queue.offer(v);
vis[v] = true;
}
}
}
}
return dist[graph.length - 1] <= k;
}
}
数学
🍕整除子串(整除定理)
被4整除,只需要考虑末尾两位能被4整除即可。所以只需要枚举末尾两位可能的组合方式,最后再加上单独每位能够被4整除的情况即可。
import java.util.*;
import java.io.*;
import java.util.List;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String input = reader.readLine().trim();
int n = input.length();
long ans = 0;
for (int i = n - 2; i >= 0; i--) {
// 只用考虑末尾两位
char a = input.charAt(i);
char b = input.charAt(i + 1);
String str = a + "" + b;
if (Integer.parseInt(str) % 4 == 0) {
ans += (i + 1); // 1是因为只考虑自身的情况
}
}
// 考虑单独一个字符
for (int i = 0; i < n; i++) {
if ((input.charAt(i) - '0') % 4 == 0) {
ans++;
}
}
System.out.println(ans);
}
}
🌯 线性筛(板子)
import java.util.*;
import java.io.*;
import java.util.List;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int[] prime = new int[101];
boolean[] isprime = new boolean[101];
int idx = 0;
for (int i = 2; i <= 100; i++) {
if (isprime[i] == false) {
prime[idx++] = i;
}
for (int j = 0; j < idx; j++) {
if (i * prime[j] > 100) break;
isprime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
}
}
🧇 质因数分解(板子)
import java.util.*;
import java.io.*;
import java.util.List;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int n = 100;
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
System.out.println(i);
n /= i;
}
}
if (n != 1) {
// 质数
System.out.println(n);
}
}
}
🥩 数字转换(快速求约数之和、树的直径问题)
问题描述:
如果一个数 x 的约数之和 y(不包括他本身)比他本身小,那么 x 可以变成 y,y 也可以变成 x。例如,4 可以变为 3,1 可以变为 7。限定所有数字变换在不超过 n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。
输入格式:
输入一个正整数 n。
输出格式:
输出不断进行数字变换且不出现重复数字的最多变换步数。
数据范围:
1 ≤ n ≤ 50000
输入样例:
7
输出样例:
3
样例解释:
一种方案为:4→3→1→7。
- 快速找某个数x的约数之和y(不含x自身),先遍历可能的倍数,速度更快
- 求解树(图)的最大路径(直径)问题
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static boolean[] vis; // 可能不止一棵树
static int ans = 0; // 全局最大值
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(reader.readLine().trim());
int[] sum = new int[n + 1];
for (int i = 1; i <= n; i++) { // 枚举倍数(约数)
for (int j = 2; j <= n / i; j++) { // 所有数字变换在不超过n的正整数范围内
// j从2开始,避免包含自身在内
sum[i * j] += i;
}
}
// 建图
List<int[]>[] graph = new LinkedList[n + 1];
for (int i = 0; i < n + 1; i++) graph[i] = new LinkedList<>();
for (int i = 1; i <= n; i++) {
if (sum[i] < i && sum[i] > 0) { // 约数之和比自身小
// 双向变换
graph[i].add(new int[] {sum[i], 1});
graph[sum[i]].add(new int[] {i, 1});
}
}
// 求解图的直径问题
vis = new boolean[n + 1];
for (int i = 1; i <= n; i++) {
// 为避免多棵树的情况,需保证每个节点都被遍历
if (vis[i]) continue;
dfs(graph, i);
}
System.out.println(ans);
}
static int dfs(List<int[]>[] graph, int u) {
vis[u] = true;
// 记录当前节点到根节点的最大值、次大值
int d1 = 0;
int d2 = 0;
for (int[] next : graph[u]) {
int v = next[0];
if (vis[v]) continue;
int w = next[1];
// 遍历得到子树的最大值情况
int d = dfs(graph, v);
if (d + w > d1) {
d2 = d1;
d1 = d + w;
} else if (d + w > d2) {
// 只能更新次大值
d2 = d + w;
}
}
// 直径 = d1 + d2
ans = Math.max(ans, d1 + d2);
return d1;
}
}
🥞 哥德巴赫猜想(线性筛)
问题描述:
哥德巴赫猜想的内容如下:
任意一个大于 4 的偶数都可以拆成两个奇素数之和。例如:
8=3+5
20=3+17=7+13
42=5+37=11+31=13+29=19+23
现在,你的任务是验证所有小于一百万的偶数能否满足哥德巴赫猜想。
输入格式
输入包含多组数据。每组数据占一行,包含一个偶数 n。读入以 0 结束。
输出格式
对于每组数据,输出形如 n = a + b,其中 a,b 是奇素数。若有多组满足条件的 a,b,输出 b − a 最大的一组。若无解,输出 Goldbach’s conjecture is wrong.。
数据范围
6 ≤ n < 10 ^ 6
输入样例:
8
20
42
0
输出样例:
8 = 3 + 5
20 = 3 + 17
42 = 5 + 37
- 奇素数,也就是奇质数,考虑到质数中只有2为偶数,其余都是奇数
- 线性筛预先找到质数
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int[] prime = new int[1000001];
boolean[] isprime = new boolean[1000001];
int idx = 0;
for (int i = 2; i <= 1000000; i++) {
if (isprime[i] == false) {
prime[idx++] = i;
}
for (int j = 0; j < idx; j++) {
if (i * prime[j] > 1000000) break;
isprime[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
while (true) {
int n = Integer.parseInt(reader.readLine().trim());
if (n == 0) break;
int i = 1; // 奇素数,从第二个素数开始
while (true) {
int a = n - prime[i];
if (isprime[a] == false) {
System.out.printf("%d = %d + %d\n", n, prime[i], a);
break;
}
i++;
}
}
}
}
🍛 夏洛克和他的girl friend(二分图、线性筛)
问题描述:
夏洛克有了一个新女友(这太不像他了!)。情人节到了,他想送给女友一些珠宝当做礼物。他买了 n 件珠宝,第 i 件的价值是 i + 1,也就是说,珠宝的价值分别为 2,3,…,n + 1。华生挑战夏洛克,让他给这些珠宝染色,使得一件珠宝的价格是另一件珠宝的价格的质因子时,两件珠宝的颜色不同。并且,华生要求他使用的颜色数尽可能少。请帮助夏洛克完成这个简单的任务。
输入格式
只有一行一个整数 n,表示珠宝件数。
输出格式
第一行一个整数 k,表示所使用的颜色数;第二行 n 个整数,表示第 1 到第 n 件珠宝被染成的颜色。若有多种答案,输出任意一种。请用 1 到 k 表示你用到的颜色。
数据范围
1 ≤ n ≤ 10 ^ 5
输入样例1:
3
输出样例1:
2
1 1 2
输入样例2:
4
输出样例2:
2
2 1 1 2
- 只有质数和合数之间有边连接,一条边的两个端点一定是一个为质数,一个为合数,只有质数无法成边,所以可以得知:构成的图为二分图,这一点非常关键。
- 如果没有合数,则没有边,全是节点,只需要一个颜色即可;如果有合数,则需要2个颜色,给质数涂1,合数涂2即可。
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
// 预处理线性筛素数
int[] prime = new int[100002];
boolean[] isprime = new boolean[100002];
int idx = 0;
for (int i = 2; i <= 100001; i++) {
if (isprime[i] == false) {
prime[idx++] = i;
}
for (int j = 0; j < idx; j++) {
if (i * prime[j] > 100001) break;
isprime[i * prime[j]] = true;
// 合数被最小质因子筛掉
if (i % prime[j] == 0) break;
}
}
int n = Integer.parseInt(reader.readLine().trim());
if (n <= 2) {
// 没有合数,只有节点
System.out.println(1);
} else {
System.out.println(2);
}
for (int i = 1; i <= n; i++) {
if (isprime[i + 1]) {
// 合数
System.out.print(2 + " ");
} else {
// 质数
System.out.print(1 + " ");
}
}
}
}
🍠 阶数分解(阶数末尾0个数类似问题)
是下面这个题目的升级版:
leetcode这道题就是说:n!能够分解出几个因子5,一个因子5对应着一个尾部0。
// 解法一:
class Solution {
public int trailingZeroes(int n) {
// 看能够分解出多少个因子5
int cnt = 0;
while (n > 0) {
cnt += n / 5;
n /= 5;
}
return cnt;
}
}
// 解法二:
class Solution {
public int trailingZeroes(int n) {
int cnt = 0;
int divisor = 5;
while (divisor <= n) {
cnt += n / divisor;
divisor *= 5;
}
return cnt;
}
}
- 用解法一的思路,无非就是因子变成了一个个质因子,之前算的是阶乘中能够拆分出多少个因子5,现在算的是从阶乘中能够拆分出多少个不同的质因子。因为算术基本定理:任何一个大于 1 的自然数可以分解成一些素数的乘积;并且在不计次序的情况下,这种分解方式是唯一的,所以我们把它拆成多个不同的质因子,最后累乘结果还是原来的数大小。
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(reader.readLine().trim());
// 预处理线性筛素数
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;
}
for (int j = 0; j < idx; j++) {
if (i * prime[j] > n) break;
isprime[i * prime[j]] = true;
// 合数被最小质因子筛掉
if (i % prime[j] == 0) break;
}
}
for (int i = 0; i < idx; i++) {
int cnt = 0;
int tmp = n;
while (tmp > 0) {
cnt += tmp / prime[i];
tmp /= prime[i];
}
writer.write(prime[i] + " " + cnt + "\n");
}
writer.flush();
}
}
🍻 序列的第k个数(快速幂)
问题描述:
BSNY 在学等差数列和等比数列,当已知前三项时,就可以知道是等差数列还是等比数列。现在给你整数序列的前三项,这个序列要么是等差序列,要么是等比序列,你能求出第 k 项的值吗。如果第 k 项的值太大,对其取模 200907。
输入格式
第一行一个整数 T,表示有 T 组测试数据;对于每组测试数据,输入前三项 a,b,c,然后输入 k。
输出格式
对于每组数据,输出第 k 项取模 200907 的值。
数据范围
1 ≤ T ≤ 100,
1 ≤ a ≤ b ≤ c ≤ 10 ^ 9,
1 ≤ k ≤ 10 ^ 9
输入样例:
2
1 2 3 5
1 2 4 5
输出样例:
5
16
-
对于非全等数列,如果2b = a + c,那么就是等差数列;如果b ^ 2 = a * c,那么就是等比数列。
-
对于全等数列,可以当作等差,也可以当作等比数列来求。
-
快速幂:指数=0,值=1;指数为偶数,只用算指数/2的结果;指数为奇数,将指数-1拆成偶数。
import java.util.*;
import java.io.*;
import java.util.List;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int t = Integer.parseInt(reader.readLine().trim());
while (t-- > 0) {
String[] input = reader.readLine().trim().split(" ");
int a = Integer.parseInt(input[0]);
int b = Integer.parseInt(input[1]);
int c = Integer.parseInt(input[2]);
int k = Integer.parseInt(input[3]);
if (2 * b == a + c) {
// 等差数列
int d = b - a; // 公差
System.out.println(a + (k - 1) * d);
} else if (b * b == a * c) {
// 等比数列
int q = b / a; // 等比
// System.out.println(a * (int)Math.pow(q, k - 1));
// 快速幂加速
int ans = a * qp(q, k - 1) % 10000007;
System.out.println(ans);
} else {
// 全等数列
System.out.println(a);
}
}
}
static int qp(int n, int p) {
if (p == 0) return 1;
else if (p % 2 == 1)
return (qp(n, p - 1) * n) % 10000007;
else {
int tmp = qp(n, p / 2);
return (tmp * tmp) % 10000007;
}
}
}
🥫 越狱(快速幂)
问题描述:
监狱有连续编号为 1 到 n 的 n 个房间,每个房间关押一个犯人。有 m 种宗教,每个犯人可能信仰其中一种。如果相邻房间的犯人信仰的宗教相同,就可能发生越狱。求有多少种状态可能发生越狱。
输入格式
共一行,包含两个整数 m 和 n。
输出格式
可能越狱的状态数,对 100003 取余。
数据范围
1 ≤ m ≤ 10 ^ 8,
1 ≤ n ≤ 10 ^ 12
输入样例:
2 3
输出样例:
6
样例解释
所有可能的 6 种状态为:(000)(001)(011)(100)(110)(111)。
- 总的可能状态:m ^ n,n个房间,每个房间都可以有m种宗教
- 只要有相邻的宗教相同,就会越狱,那就考虑相邻的宗教不同的情况,第一个房间有m种选择,第二个房间只有m-1个选择,第三个房间也有m-1种选择,后面的所有房间都有m-1种选择,即:m * (m - 1) ^ (n - 1)
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] input = reader.readLine().trim().split(" ");
long m = Long.parseLong(input[0]);
long n = Long.parseLong(input[1]);
long a = qp(m, n);
long b = (m * qp(m - 1, n - 1)) % 100003;
// 由于a b都是经过取余的,可能a小于b,但实际情况是a大于b
// 所以要加上mod,再对mod取余
System.out.println((a - b + 100003) % 100003);
}
static long qp(long n, long p) {
if (p == 0) return 1;
else if (p % 2 == 1)
return (qp(n, p - 1) * n) % 100003;
else {
long tmp = qp(n, p / 2);
return tmp * tmp % 100003;
}
}
}
🍶 轻拍牛头(由倍数确定约数个数)
问题描述:
今天是贝茜的生日,为了庆祝自己的生日,贝茜邀你来玩一个游戏.贝茜让 N 头奶牛(编号 1 到 N)坐成一个圈。除了 1 号与 N 号奶牛外,i 号奶牛与 i−1 号和 i+1 号奶牛相邻,N 号奶牛与 1 号奶牛相邻。农夫约翰用很多纸条装满了一个桶,每一张纸条中包含一个 1 到 1000000 之间的数字。接着每一头奶牛 i 从桶中取出一张纸条,纸条上的数字用 Ai 表示。所有奶牛都选取完毕后,每头奶牛轮流走上一圈,当走到一头奶牛身旁时,如果自己手中的数字能够被该奶牛手中的数字整除,则拍打该牛的头。牛们希望你帮助他们确定,每一头奶牛需要拍打的牛的数量。即共有 N 个整数 A1,A2,…,AN,对于每一个数 Ai,求其他的数中有多少个是它的约数。
输入格式
第一行包含整数 N。接下来 N 行,每行包含一个整数 Ai。
输出格式
共 N 行,第 i 行的数字为第 i 头牛需要拍打的牛的数量。
数据范围
1 ≤ N ≤ 10 ^ 5,
1 ≤ Ai ≤ 10 ^ 6
输入样例:
5
2
1
2
3
4
输出样例:
2
0
2
1
3
- 题目的意思已经告诉了即共有 N 个整数 A1,A2,…,AN,对于每一个数 Ai,求其他的数中有多少个是它的约数。
- 从倍数的角度出发,因为当前数Ai的倍数的约数一定是Ai,那么Ai有多少个,该倍数的约数就可以多添多少个。
import java.util.*;
import java.io.*;
public class Main {
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(reader.readLine().trim());
int[] cow = new int[n];
// 统计每个数出现的次数
int[] cnt = new int[1000010];
for (int i = 0; i < n; i++) {
cow[i] = Integer.parseInt(reader.readLine().trim());
cnt[cow[i]]++; // 该数出现
}
int[] ans = new int[1000010];
for (int i = 1; i < 1000010; i++) { // 枚举每个数
if (cnt[i] == 0) continue; // 这个数没有出现过
for (int j = i; j < 1000010; j += i) {
// 这个数的倍数的约数,就是这个数
ans[j] += cnt[i]; // 对应的该数的倍数的约数个数,就等于这个数出现的次数
}
}
for (int i = 0; i < n; i++) {
System.out.println(ans[cow[i]] - 1); // -1是去掉自身这个约数
}
}
}
🍯 樱花
问题描述:
给定一个整数 n,求有多少正整数数对 (x,y) 满足 1 / x + 1 / y = 1 / n!。
输入格式
一个整数 n。
输出格式
一个整数,表示满足条件的数对数量。答案对 10 ^ 9 + 7 取模。
数据范围
1 ≤ n ≤ 10 ^ 6
输入样例:
2
输出样例:
3
样例解释
共有三个数对 (x,y) 满足条件,分别是 (3,6),(4,4),(6,3)。