java 洛谷题单【数学1】基础数学问题
P1143 进制转换
解题思路
- 读取输入:
- 使用 Scanner 读取三个输入值:原进制 n,原进制数 s,目标进制 m。
- 进制转换:
- 使用
Integer.parseInt(s, n)
将原进制数 s 转换为十进制整数。- 使用
Integer.toString(decimalValue, m)
将十进制整数转换为目标进制字符串。- 调用
.toUpperCase()
确保输出中的字母为大写。- 输出结果:
- 打印转换后的目标进制数。
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
// 读取输入
int n = input.nextInt(); // 原进制
String s = input.next(); // 原进制数
int m = input.nextInt(); // 目标进制
// 关闭输入流
input.close();
// 将原进制数转换为目标进制
int decimalValue = Integer.parseInt(s, n);
String result = Integer.toString(decimalValue, m).toUpperCase();
// 输出结果
System.out.println(result);
}
}
P1469 找筷子
解题思路
对于对内存有要求的题目不太适合使用Java完成,并且有规定不保证非C/C++语言可以通过题目,洛谷不会给非C/C++语言提供额外的时间和空间,所以该题大家最好使用C/C++完成,Java代码仅供参考。
该题通过异或运算的思路可以完美解决配对问题,异或运算的法则是相同为0,不同为1
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int ans = 0;
while (n-- > 0) {
ans ^= scanner.nextInt();
}
System.out.println(ans);
scanner.close();
}
}
P1100 高低位交换
解题思路
1. 读取输入
- 使用 Scanner 读取一个整数
num
。- input.nextInt() 读取的是一个有符号的 32 位整数。
- 为了将其视为无符号整数,使用按位与操作
& 0xFFFFFFFFL
:
0xFFFFFFFFL
是一个 64 位的掩码,保留输入整数的低 32 位。- 这样可以将负数转换为其无符号等价值(例如,
-1
会被转换为4294967295
)。
2. 高低位交换
提取低 16 位(unsignedNum & 0x0000ffff):
- 使用按位与操作
& 0x0000ffff
提取无符号数的低 16 位。- 例如,
unsignedNum = 0x12345678
,则低 16 位为0x5678
。将低 16 位移到高 16 位((unsignedNum & 0x0000ffff) << 16):
- 使用左移操作
<< 16
将低 16 位移动到高 16 位的位置。提取高 16 位(unsignedNum & 0xffff0000):
- 使用按位与操作
& 0xffff0000
提取无符号数的高 16 位。- 例如,
unsignedNum = 0x12345678
,则高 16 位为0x1234
。将高 16 位移到低 16 位((unsignedNum & 0xffff0000) >> 16):
- 使用右移操作
>> 16
将高 16 位移动到低 16 位的位置。- 注意:由于 unsignedNum 是
long
类型,右移操作不会保留符号位。合并高低位(((unsignedNum & 0x0000ffff) << 16 | (unsignedNum & 0xffff0000) >> 16)):
- 使用按位或操作
|
将交换后的高位和低位合并,得到最终结果。
3. 输出结果
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
// 将读入的数转为无符号整型
long unsignedNum = input.nextInt() & 0xFFFFFFFFL;
// 输出结果
System.out.println(((unsignedNum & 0x0000ffff) << 16 | (unsignedNum & 0xffff0000) >> 16));
input.close();
}
}
P1017 [NOIP 2000 提高组] 进制转换
解题思路
输入读取:
- 使用 Scanner 读取十进制数
n
和负进制基数r
。负进制转换逻辑:
- 使用
n % r
计算余数。- 如果余数为负数,则调整余数为正数,同时将商
n
加 1。- 将余数转换为对应的字符:
- 如果余数大于等于 10,使用
A
到F
表示(类似于 16 进制)。- 否则直接添加数字。
结果构建:
- 使用
StringBuilder
构建结果,最后调用reverse()
方法将结果反转(因为进制转换是从低位到高位计算的)。特殊情况:
- 如果输入的
n
为 0,直接输出0
。输出格式:
- 按照题目要求输出:
n=结果(baseR)
。
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
// 读取输入的十进制数 n 和负进制基数 R
int n = input.nextInt();
int r = input.nextInt();
// 保存原始输入值,用于输出
int originalN = n;
// 用 StringBuilder 构建结果
StringBuilder result = new StringBuilder();
// 负进制转换
while (n != 0) {
int remainder = n % r;
n /= r;
// 如果余数为负数,调整余数和商
if (remainder < 0) {
remainder += Math.abs(r);
n += 1;
}
// 将余数转换为对应的字符(支持大于 10 的数码)
if (remainder >= 10) {
result.append((char) ('A' + (remainder - 10)));
} else {
result.append(remainder);
}
}
// 如果结果为空,表示输入为 0
if (result.length() == 0) {
result.append("0");
}
// 输出结果
System.out.printf("%d=%s(base%d)\n", originalN, result.reverse().toString(), r);
input.close();
}
}
P1866 编号
解题思路
1.排序:将兔子的编号范围按升序排序,优先处理编号范围较小的兔子。
2.计算可选编号:对于第 i 只兔子,可选编号数量为
M[i] - i
,因为前面 i 只兔子已经占用了 i 个编号。3.取模运算:每次乘法操作后对
取模,避免溢出。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws Exception {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int[] m = new int[n];
for (int i = 0; i < m.length; i++) {
m[i] = input.nextInt();
}
input.close();
Arrays.sort(m);
// mod
final int MOD = 1_000_000_007;
long ans = 1;
for (int i = 0; i < n; i++) {
int choices = m[i] - i;
if (choices <= 0) {
ans = 0;
break;
}
ans = (ans * choices) % MOD;
}
System.out.println(ans);
}
}
P2822 [NOIP 2016 提高组] 组合数问题
解题思路
(1) 初始化组合数
- 使用递推公式计算组合数:
- 每次计算后对 $k$ 取模,避免溢出。
- 如果
,则统计该组合数。
(2) 计算前缀和
- 对于每行
,计算满足条件的组合数的前缀和。
- 前缀和公式:
- 如果
:
- 如果
(对角线特殊处理):
(3) 查询结果
- 对于每个测试用例,读取
和
。
- 确保
,因为列数不能超过行数。
- 直接通过 prefixSum 查询满足条件的组合数个数。
import java.util.Scanner;
public class Main {
static long[][] comb = new long[2001][2001]; // 存储组合数
static long[] count = new long[2001]; // 每行满足条件的组合数个数
static long[][] prefixSum = new long[2001][2001]; // 前缀和数组
static long n, m, k;
static int t;
public static void main(String[] args) {
Scanner ipnut = new Scanner(System.in);
t = ipnut.nextInt();
k = ipnut.nextLong();
// 初始化组合数
comb[0][0] = 1;
for (int i = 1; i < 2001; i++) {
comb[i][0] = 1;
for (int j = 1; j <= i; j++) {
// 递推计算组合数并取模
comb[i][j] = (comb[i - 1][j - 1] + comb[i - 1][j]) % k;
if (comb[i][j] == 0) {
// 统计每行满足条件的组合数个数
count[i]++;
}
// 计算前缀和
prefixSum[i][j] = prefixSum[i - 1][j] + count[i];
if (j == i) {
// 特殊处理对角线
prefixSum[i][j] = count[i] + prefixSum[i - 1][j - 1];
}
}
}
while (t-- > 0) {
// 读取输入
n = ipnut.nextLong();
m = ipnut.nextLong();
// 确保列数不超过行数
if (m > n) {
m = n;
}
// 输出结果
System.out.println(prefixSum[(int) n][(int) m]);
}
ipnut.close();
}
}
P2789 直线交点数
解题思路
本来没有思路,看了题解才明白,代码思路来源于洛谷题解。觉得有帮助的可以给题解作者点个免费的赞。
import java.util.Scanner;
public class Main {
static int n;
static int MAX = -1;
static int ans = 0;
static boolean[] f = new boolean[11000];
/**
* 递归函数,用于计算所有可能的结果
*
* @param n 剩余的数
* @param k 当前计算的结果
*/
public static void func(int n, int k) {
// 如果剩余的数为 0,记录当前结果
if (n == 0) {
f[k] = true; // 标记结果 k 为可达
MAX = Math.max(k, MAX); // 更新最大值
} else {
// 遍历所有可能的相交方式
for (int r = n; r >= 1; r--) {
// 递归调用,计算剩余部分
func(n - r, r * (n - r) + k);
}
}
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
n = input.nextInt();
func(n, 0);
for (int i = 0; i <= MAX; i++) {
if (f[i]) {
ans++;
}
}
System.out.println(ans);
input.close();
}
}
P3913 车的攻击
解题思路
1. 排序
- 对 rows 和 cols 数组进行排序:
- 排序的目的是为了方便后续统计数组中不同元素的数量(去重)。
2. 统计不同行和列的数量
- 调用 countDistinct 方法分别统计 rows 和 cols 中不同元素的数量:
- r 表示不同的行数。
- c 表示不同的列数。
- countDistinct 方法的逻辑:
- 遍历排序后的数组,统计相邻元素不同的次数,即为数组中不同元素的数量。
3. 计算覆盖的格子数量
- 使用公式计算覆盖的格子数量:
long result = (long) N * (r + c) - (long) r * c;
(N * (r + c))
:表示所有被覆盖的行和列的格子数。-(r * c)
:减去行列交叉的格子数,因为这些格子会被重复计算。
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
// 使用 BufferedReader 读取输入,提高输入效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 使用 StringTokenizer 解析第一行输入,获取 N 和 K
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken()); // 矩阵的大小 N x N
int K = Integer.parseInt(st.nextToken()); // 特殊点的数量 K
// 定义两个数组分别存储特殊点的行和列
int[] rows = new int[K];
int[] cols = new int[K];
// 读取 K 个特殊点的行列坐标
for (int i = 0; i < K; i++) {
st = new StringTokenizer(br.readLine());
rows[i] = Integer.parseInt(st.nextToken()); // 读取行坐标
cols[i] = Integer.parseInt(st.nextToken()); // 读取列坐标
}
// 对行和列的数组进行排序,方便后续去重统计
Arrays.sort(rows);
Arrays.sort(cols);
// 统计行和列中不同的值的数量
int r = countDistinct(rows); // 不同行的数量
int c = countDistinct(cols); // 不同列的数量
// 计算结果:总覆盖的格子数
// (N * (r + c)) 表示所有被覆盖的行和列的格子数
// 减去 (r * c),因为行列交叉的格子会被重复计算
long result = (long) N * (r + c) - (long) r * c;
// 输出结果
System.out.println(result);
}
/**
* 统计数组中不同元素的数量
* @param array 已排序的数组
* @return 不同元素的数量
*/
private static int countDistinct(int[] array) {
if (array.length == 0)
return 0; // 如果数组为空,返回 0
int count = 1; // 至少有一个不同的元素
for (int i = 1; i < array.length; i++) {
// 如果当前元素与前一个元素不同,计数加一
if (array[i] != array[i - 1]) {
count++;
}
}
return count; // 返回不同元素的数量
}
}
P2638 安全系统
解题思路
1. 核心功能
代码的核心是计算组合数(即数学中的 C(n, r)),并将结果进行某种组合运算后输出。组合数的公式为:
由于可能涉及非常大的数,代码使用了 Java 的 BigInteger 类来处理大数运算。
2. 代码结构
func 方法
- 该方法用于计算组合数 C(n, r)。
- 通过迭代的方式计算组合数,避免直接计算阶乘(因为阶乘增长非常快,容易导致性能问题或溢出)。
- 具体实现:
- 使用一个循环从 1 到 r,逐步计算组合数的分子和分母。
- 分子:
n * (n - 1) * ... * (n - r + 1)
- 分母:r!
- 每一步都用 BigInteger 的 multiply 和 divide 方法进行计算。
main 方法
- 主方法负责读取输入、调用计算方法并输出结果。
- 输入:
- 从控制台读取三个 BigInteger 类型的数:n、a 和 b。
- 计算:
- 调用 func 方法计算两个组合数:
- func(n, a + n)
- func(n, b + n)
- 将两个组合数的结果相乘。
- 输出:
- 使用 write 方法输出最终结果。
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
// 输出 BigInteger 类型的结果
public static void write(BigInteger x) {
System.out.print(x);
}
// 计算组合数
public static BigInteger func(BigInteger r, BigInteger n) {
BigInteger ans = BigInteger.ONE;
// 计算组合数的公式 C(n, r) = n! / (r! * (n - r)!)
// 这里使用了 BigInteger 的方法来处理大数
for (BigInteger i = BigInteger.ONE; i.compareTo(r) <= 0; i = i.add(BigInteger.ONE)) {
ans = ans.multiply(n.subtract(i).add(BigInteger.ONE));
ans = ans.divide(i);
}
return ans;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
BigInteger n = input.nextBigInteger();
BigInteger a = input.nextBigInteger();
BigInteger b = input.nextBigInteger();
input.close();
// 代入公式计算并输出结果
write(func(n, a.add(n)).multiply(func(n, b.add(n))));
}
}
P1246 编码
解题思路
输入验证:检查输入字符串是否由小写字母组成,并且每个字符严格递增。
计算前缀和:统计所有比当前单词长度短的合法单词数目之和。
计算当前长度内的位置:使用组合数学的方法计算当前单词在其长度内的位置。
结果输出:将前缀和与当前长度内的位置相加得到最终编码。
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
// 使用 BufferedReader 读取输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine().trim(); // 读取并去除首尾空格
int len = s.length(); // 获取输入字符串的长度
// 检查字符串长度是否符合要求(1 <= len <= 6)
if (len < 1 || len > 6) {
System.out.println(0); // 长度不合法,输出 0
return;
}
int[] chars = new int[len]; // 用于存储字符对应的字母序号
for (int i = 0; i < len; i++) {
char c = s.charAt(i); // 获取字符串中的每个字符
// 检查字符是否为小写字母
if (c < 'a' || c > 'z') {
System.out.println(0); // 非法字符,输出 0
return;
}
chars[i] = c - 'a' + 1; // 将字符转换为字母序号(a=1, b=2, ..., z=26)
}
// 检查字符是否按严格递增顺序排列
for (int i = 0; i < len - 1; i++) {
if (chars[i] >= chars[i + 1]) {
System.out.println(0); // 非递增,输出 0
return;
}
}
int m = len; // 字符串长度
long sum = 0; // 用于存储比当前长度短的所有合法字符串的数量
// 计算比当前长度短的所有合法字符串数量
for (int i = 1; i < m; i++) {
sum += comb(26, i); // 从 26 个字母中选 i 个的组合数
}
long pos = 0; // 当前字符串在所有合法字符串中的相对位置
int prev = 0; // 上一个字符的字母序号
// 计算当前字符串的相对位置
for (int i = 0; i < m; i++) {
int current = chars[i]; // 当前字符的字母序号
int start = prev + 1; // 起始字母序号
int end = current - 1; // 结束字母序号
// 遍历从 start 到 end 的所有可能字符
for (int j = start; j <= end; j++) {
int remain = m - i - 1; // 剩余未处理的字符数
pos += comb(26 - j, remain); // 计算剩余字符的组合数
}
prev = current; // 更新上一个字符的字母序号
}
pos += 1; // 加上当前字符串本身的位置
// 输出结果:比当前字符串小的所有合法字符串数量 + 当前字符串的位置
System.out.println(sum + pos);
}
// 计算组合数 C(n, k)
private static int comb(int n, int k) {
if (k < 0 || k > n) // 如果 k 不合法,返回 0
return 0;
if (k == 0 || k == n) // 如果 k 为 0 或 n,返回 1
return 1;
if (k > n / 2) // 优化计算,C(n, k) = C(n, n-k)
k = n - k;
int result = 1; // 存储结果
for (int i = 1; i <= k; i++) {
result = result * (n - k + i) / i; // 使用公式计算组合数
}
return result;
}
}
P2926 [USACO08DEC] Patting Heads S
解题思路
内存优化策略
使用
HashMap
替代大数组:当数值范围较大时(如最大值为1e9),传统数组会占用过多内存。哈希表只存储实际存在的数值,显著减少内存消耗。按需计算:仅处理实际存在的数值,避免遍历整个数值范围。
核心算法逻辑
因数替代倍数:原题要求统计数值的倍数,但优化后改为统计因数。因为当数值范围很大时,因数的数量远小于倍数的数量。
预处理机制:预先计算每个数值的拍打次数并存储,后续查询时间复杂度降为O(1)。
关键操作说明
因数生成:通过遍历到
来高效获取所有因数,时间复杂度为
。
拍打次数计算:对每个数值的因数集合,累加这些因数在输入中的出现次数总和。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int N = input.nextInt(); // 读取牛的总数
int[] A = new int[N]; // 存储每头牛的数值
Map<Integer, Integer> countMap = new HashMap<>(); // 统计数值出现次数
// 统计每个数的出现次数
for (int i = 0; i < N; i++) {
A[i] = input.nextInt();
// 使用哈希表记录数值出现次数(自动处理稀疏数据)
countMap.put(A[i], countMap.getOrDefault(A[i], 0) + 1);
}
// 预处理每个数值的拍打次数
Map<Integer, Integer> patsMap = new HashMap<>(); // 存储数值对应的拍打总次数
for (int num : countMap.keySet()) { // 只处理实际存在的数值
int sum = 0;
// 获取当前数值的所有因数(包括1和自身)
List<Integer> divisors = getDivisors(num);
// 累加所有因数的出现次数
for (int d : divisors) {
sum += countMap.getOrDefault(d, 0);
}
patsMap.put(num, sum); // 存储计算结果
}
// 输出结果(需减去自身的一次计数)
for (int x : A) {
System.out.println(patsMap.get(x) - 1);
}
input.close();
}
// 生成一个数的所有因数
private static List<Integer> getDivisors(int n) {
List<Integer> divisors = new ArrayList<>();
// 遍历到sqrt(n)以优化效率
for (int i = 1; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
divisors.add(i); // 添加较小因数
int other = n / i; // 计算对应较大因数
if (other != i) { // 避免平方数重复添加
divisors.add(other);
}
}
}
return divisors;
}
}
P3383 【模板】线性筛素数
解题思路
线性筛法:
- 使用
isComposite
数组标记非素数。- 通过
primes
列表存储所有素数。- 保证每个数只被其最小的素因子标记一次,时间复杂度为
。
查询处理:
- 直接通过
primes.get(k - 1)
获取第小的素数
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
// 读取 n 和 q
String[] firstLine = br.readLine().split(" ");
int n = Integer.parseInt(firstLine[0]);
int q = Integer.parseInt(firstLine[1]);
// 线性筛法求素数
List<Integer> primes = new ArrayList<>();
boolean[] isComposite = new boolean[n + 1];
for (int i = 2; i <= n; i++) {
if (!isComposite[i]) {
primes.add(i);
}
for (int prime : primes) {
if (i * prime > n) break;
isComposite[i * prime] = true;
if (i % prime == 0) break;
}
}
// 处理查询
for (int i = 0; i < q; i++) {
int k = Integer.parseInt(br.readLine());
pw.println(primes.get(k - 1)); // 第 k 小的素数
}
pw.flush();
}
}
P1835 素数密度
解题思路
埃拉托色尼筛法:
- 先标记区间
[l, r]
中所有数为素数。- 从小于等于
sqrt(r)
的所有素数开始,将它们的倍数标记为非素数。- 通过计算起始位置
start
,避免从 1 开始筛选,直接筛选区间内的数。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
long l = input.nextLong();
long r = input.nextLong();
input.close();
System.out.println(func(l, r));
}
public static int func(long l, long r) {
// 使用埃拉托色尼筛法优化
return countPrimesInRange(l, r);
}
public static int countPrimesInRange(long l, long r) {
// 标记区间 [l, r] 中的数是否为素数
boolean[] isPrime = new boolean[(int) (r - l + 1)];
Arrays.fill(isPrime, true);
// 从 2 开始筛选素数
for (long i = 2; i * i <= r; i++) {
long start = Math.max(i * i, (l + i - 1) / i * i); // 找到区间内第一个 i 的倍数
for (long j = start; j <= r; j += i) {
isPrime[(int) (j - l)] = false;
}
}
// 特殊处理 1 不是素数
if (l == 1) {
isPrime[0] = false;
}
// 统计素数个数
int count = 0;
for (boolean prime : isPrime) {
if (prime) {
count++;
}
}
return count;
}
}
P1029 [NOIP 2001 普及组] 最大公约数和最小公倍数问题
解题思路
理解条件:
和
的最大公约数是
。
和
的最小公倍数是
。
- 根据数学性质,
,因此
。
优化计算:
- 令
,
必须是整数。
- 我们需要找到所有的正整数对
,使得
且
。
- 对于每个这样的
,我们可以构造
和
。
实现代码:
- 遍历
的所有因子,检查每对因子是否满足
。
- 统计满足条件的因子对数量。
注意事项:
输入检查:
- 如果
不是
的倍数,则直接输出
0
,因为无法满足条件。- 如果
,直接返回1,两个数相等时只有一个结果就是它们本身。
因子遍历:
- 遍历
的所有因子对
,并检查
。
- 每找到一个因子对
,计数加 2,因为
和
都是有效解。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int x0 = input.nextInt();
int y0 = input.nextInt();
input.close();
// 如果 y0 不是 x0 的倍数,则无解
if (y0 % x0 != 0) {
System.out.println(0);
return;
}
if (x0 == y0) {
System.out.println(1);
return;
}
int z = y0 / x0; // z = y0 / x0
int count = 0;
// 遍历 z 的所有因子
for (int a = 1; a * a <= z; a++) {
if (z % a == 0) {
int b = z / a; // 因子对 (a, b)
// 检查 gcd(a, b) 是否为 1
if (gcd(a, b) == 1) {
count += 2; // (a, b) 和 (b, a) 都满足条件
}
}
}
System.out.println(count);
}
// 辗转相除法计算 gcd
private static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
P1072 [NOIP 2009 提高组] Hankson 的趣味题
解题思路
枚举
:
必须满足
,因此
的范围是
的所有因子。
验证条件:
- 对于每个
,验证:
。
。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt(); // 读取测试组数
StringBuilder result = new StringBuilder();
while (n-- > 0) {
long a0 = input.nextLong();
long a1 = input.nextLong();
long b0 = input.nextLong();
long b1 = input.nextLong();
// 如果 a1 不是 a0 的因子,或者 b1 不是 b0 的倍数,则无解
if (a0 % a1 != 0 || b1 % b0 != 0) {
result.append(0).append("\n");
continue;
}
// 枚举 k 的所有可能值
long xBase = a1; // x = k * a1
long limit = b1 / xBase; // k 的最大值
int count = 0;
for (long k = 1; k * k <= limit; k++) {
if (limit % k == 0) {
// 检查两个因子 k 和 limit / k
if (isValid(k, xBase, a0, b0, b1)) count++;
if (k != limit / k && isValid(limit / k, xBase, a0, b0, b1)) count++;
}
}
result.append(count).append("\n");
}
input.close();
System.out.print(result);
}
// 检查 k 是否满足条件
private static boolean isValid(long k, long xBase, long a0, long b0, long b1) {
long x = k * xBase;
return gcd(x, a0) == xBase && lcm(x, b0) == b1;
}
// 辗转相除法计算 gcd
private static long gcd(long a, long b) {
while (b != 0) {
long temp = b;
b = a % b;
a = temp;
}
return a;
}
// 计算 lcm
private static long lcm(long a, long b) {
return a / gcd(a, b) * b;
}
}
P1069 [NOIP 2009 普及组] 细胞分裂
解题思路
输入处理:读取细胞种数、试管参数和每个细胞的分裂数。
特殊情况处理:如果
为1,直接输出0,因为任何细胞都能立即满足条件。
质因数分解:将
分解质因数,并计算其在
中的指数。
细胞有效性检查:对于每个细胞的分裂数,检查是否包含
M
的所有质因数,并计算每个质因数的指数。时间计算:针对每个有效细胞,计算满足条件所需的最长时间,并更新全局最短时间。
结果输出:根据是否存在有效细胞输出最短时间或-1。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int N = input.nextInt();
int m1 = input.nextInt();
int m2 = input.nextInt();
int[] S = new int[N];
for (int i = 0; i < N; i++) {
S[i] = input.nextInt();
}
// 处理m1=1的特殊情况,任何情况都能在0秒完成
if (m1 == 1) {
System.out.println(0);
return;
}
// 分解m1的质因数
Map<Integer, Integer> m1Factors = primeFactors(m1);
// 计算M的质因数分解(每个指数乘以m2)
Map<Integer, Integer> mFactors = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : m1Factors.entrySet()) {
mFactors.put(entry.getKey(), entry.getValue() * m2);
}
int minTime = Integer.MAX_VALUE;
for (int s : S) {
boolean valid = true;
Map<Integer, Integer> sFactors = new HashMap<>();
// 检查s是否包含所有M的质因数
for (int p : mFactors.keySet()) {
if (s % p != 0) {
valid = false;
break;
}
// 计算s中质因数p的指数
int e = 0;
int temp = s;
while (temp % p == 0) {
e++;
temp /= p;
}
sFactors.put(p, e);
}
if (!valid)
continue;
// 计算当前s所需的最大时间
int currentMax = 0;
for (Map.Entry<Integer, Integer> entry : mFactors.entrySet()) {
int p = entry.getKey();
int requiredExp = entry.getValue(); // M中p的指数
int sExp = sFactors.get(p); // s中p的指数
// 向上取整计算所需时间
int t = (requiredExp + sExp - 1) / sExp;
if (t > currentMax) {
currentMax = t;
}
}
// 更新全局最小时间
if (currentMax < minTime) {
minTime = currentMax;
}
}
System.out.println(minTime == Integer.MAX_VALUE ? -1 : minTime);
input.close();
}
// 质因数分解方法
private static Map<Integer, Integer> primeFactors(int n) {
Map<Integer, Integer> factors = new HashMap<>();
if (n == 1)
return factors;
// 处理2的因数
while (n % 2 == 0) {
factors.put(2, factors.getOrDefault(2, 0) + 1);
n /= 2;
}
// 处理奇数因数
for (int i = 3; i * i <= n; i += 2) {
while (n % i == 0) {
factors.put(i, factors.getOrDefault(i, 0) + 1);
n /= i;
}
}
// 处理剩余的质因数
if (n > 2) {
factors.put(n, 1);
}
return factors;
}
}
P1572 计算分数
解题思路
- 解析输入:
- 将输入的分数表达式拆分为多个分数项,每个分数项形如
a/b
。- 处理分数项之间的运算符(
+
或-
)。- 分数运算:
- 使用分数的加减法公式:
- 每次计算后化简分数(求最大公约数 GCD)。
- 化简分数:
- 使用欧几里得算法求最大公约数(GCD),将分子和分母同时除以 GCD。
- 输出结果:
- 如果分母为 1,则输出整数。
- 否则输出最简分数。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String expression = input.nextLine();
input.close();
// 初始化分子和分母
int numerator = 0; // 当前分子
int denominator = 1; // 当前分母
// 正则表达式匹配分数项
String[] terms = expression.split("(?=[+-])"); // 按 `+` 或 `-` 分割
for (String term : terms) {
// 分割分子和分母
String[] fraction = term.split("/");
int num = Integer.parseInt(fraction[0]); // 分子
int den = Integer.parseInt(fraction[1]); // 分母
// 计算通分后的分子和分母
numerator = numerator * den + num * denominator;
denominator *= den;
// 化简分数
int gcd = gcd(Math.abs(numerator), denominator);
numerator /= gcd;
denominator /= gcd;
}
// 输出结果
if (denominator == 1) {
System.out.println(numerator); // 如果分母为 1,输出整数
} else {
System.out.println(numerator + "/" + denominator); // 输出最简分数
}
}
// 求最大公约数(GCD)
private static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}
P4057 [Code+#1] 晨跑
解题思路
- 问题分析:
- 三位同学的晨跑周期分别为
a
、b
和c
。- 他们在第 0 天相遇,下一次相遇的天数是
a
、b
和c
的最小公倍数(LCM)。- 计算最小公倍数(LCM):
- 最小公倍数公式:
- 其中 GCD 是最大公约数,可以使用欧几里得算法计算。
- 扩展到三个数:
- 先计算两个数的 LCM,然后将结果与第三个数计算 LCM。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 读取输入
long a = input.nextLong();
long b = input.nextLong();
long c = input.nextLong();
input.close();
// 计算三者的最小公倍数
long lcmAB = lcm(a, b); // a 和 b 的最小公倍数
long lcmABC = lcm(lcmAB, c); // lcmAB 和 c 的最小公倍数
// 输出结果
System.out.println(lcmABC);
}
// 求最大公约数(GCD)
private static long gcd(long x, long y) {
return y == 0 ? x : gcd(y, x % y);
}
// 求最小公倍数(LCM)
private static long lcm(long x, long y) {
return x / gcd(x, y) * y; // 防止溢出,先除后乘
}
}
P1414 又是毕业季II
解题思路
输入处理与频率统计
读取输入数据并统计每个数出现的次数,记录在freq
数组中,同时确定最大值max_x
。因数统计
对每个存在的数生成其所有因数,并统计每个因数的倍数总次数cnt[d]
。例如,若数x
出现freq[x]
次,则其所有因数d
对应的cnt[d]
增加freq[x]
。因数降序处理
将所有可能的因数按降序排列,确保后续处理时优先考虑较大的因数。填充答案数组
遍历降序后的因数列表,根据每个因数d
的倍数总数cnt[d]
,更新答案数组ans
。对于每个d
,若其倍数总数c
大于当前已覆盖的最大值max_so_far
,则填充ans
数组的未覆盖区间,确保每个k
取到最大可能的d
。输出结果
按顺序输出ans[1]
到ans[n]
,即每个k
对应的最大公约数。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int[] a = new int[n];
int max_x = 0;
int[] freq = new int[1000002]; // 存储每个数的出现次数,最大1e6+1
// 读取输入并统计频率
for (int i = 0; i < n; i++) {
a[i] = input.nextInt();
freq[a[i]]++;
if (a[i] > max_x) {
max_x = a[i];
}
}
int[] cnt = new int[max_x + 2]; // cnt[d]表示d的倍数的总个数
Set<Integer> divisorsSet = new HashSet<>();
// 统计所有因数及其出现次数
for (int x = 1; x <= max_x; x++) {
if (freq[x] == 0)
continue;
List<Integer> divisors = getDivisors(x);
for (int d : divisors) {
cnt[d] += freq[x];
divisorsSet.add(d);
}
}
// 将因数按降序排列
List<Integer> divisorsList = new ArrayList<>(divisorsSet);
Collections.sort(divisorsList, Collections.reverseOrder());
int[] ans = new int[n + 1]; // ans[k]表示选k人时的最大gcd
int max_so_far = 0;
// 填充答案数组
for (int d : divisorsList) {
int c = cnt[d];
if (c == 0)
continue;
if (c > max_so_far) {
int start = max_so_far + 1;
int end = Math.min(c, n);
if (start > end)
continue;
for (int k = start; k <= end; k++) {
ans[k] = d;
}
max_so_far = end;
if (max_so_far == n)
break; // 提前终止
}
}
// 输出结果
for (int k = 1; k <= n; k++) {
System.out.println(ans[k]);
}
input.close();
}
// 生成一个数的所有因数
private static List<Integer> getDivisors(int x) {
List<Integer> divisors = new ArrayList<>();
for (int i = 1; i * i <= x; i++) {
if (x % i == 0) {
divisors.add(i);
int other = x / i;
if (other != i) {
divisors.add(other);
}
}
}
return divisors;
}
}
P2651 添加括号III
解题思路
输入处理:使用
Scanner
读取输入数据,处理多个测试用例。特殊情况处理:当分母为1时,直接输出"Yes"。
质因数分解函数:对给定的数进行质因数分解,返回质因数及其指数的映射。
统计质因数次数:遍历分子部分的每个数,统计所有质因数的总次数。
结果判断:检查分母的每个质因数是否在分子中有足够的次数覆盖,若全部满足则输出"Yes",否则输出"No"。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int t = input.nextInt();
while (t-- > 0) {
int n = input.nextInt();
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = input.nextInt();
}
if (n < 2) {
System.out.println("Yes");
continue;
}
int a2 = a[1];
if (a2 == 1) {
System.out.println("Yes");
continue;
}
// 计算第二个元素的质因数分解结果
Map<Integer, Integer> m2Factors = primeFactors(a2);
// 如果没有质因数,输出yes跳到下一个元素
if (m2Factors.isEmpty()) {
System.out.println("Yes");
continue;
}
int a1 = a[0];
// 计算第一个元素的质因数分解结果
Map<Integer, Integer> total = primeFactors(a1);
// 从数组的第三个元素开始,依次计算每个元素的质因数分解结果
for (int i = 2; i < n; i++) {
int x = a[i];
// 计算当前元素的质因数分解结果
Map<Integer, Integer> xFactors = primeFactors(x);
// 遍历当前元素的质因数分解结果
for (Map.Entry<Integer, Integer> entry : xFactors.entrySet()) {
// 获取质因数
int p = entry.getKey();
// 获取该质因数的个数
int cnt = entry.getValue();
// 将该质因数的个数累加到 total 中
total.put(p, total.getOrDefault(p, 0) + cnt);
}
}
boolean ok = true;
// 遍历第二个元素的质因数分解结果
for (Map.Entry<Integer, Integer> entry : m2Factors.entrySet()) {
int p = entry.getKey();
int required = entry.getValue();
int actual = total.getOrDefault(p, 0);
if (actual < required) {
ok = false;
break;
}
}
System.out.println(ok ? "Yes" : "No");
}
input.close();
}
// 计算一个数的质因数分解结果
private static Map<Integer, Integer> primeFactors(int x) {
// 用于存储质因数及其个数的映射
Map<Integer, Integer> factors = new HashMap<>();
if (x == 1) {
return factors;
}
// 处理 2 这个质因数
while (x % 2 == 0) {
factors.put(2, factors.getOrDefault(2, 0) + 1);
x /= 2;
}
// 从 3 开始,以 2 为步长遍历奇数,直到 sqrt(x)
for (int i = 3; i <= Math.sqrt(x); i += 2) {
while (x % i == 0) {
// 更新当前质因数的个数
factors.put(i, factors.getOrDefault(i, 0) + 1);
// 将 x 除以当前质因数
x /= i;
}
}
// 如果 x 大于 2,说明 x 本身是一个质数,将其作为质因数添加到映射中
if (x > 2) {
factors.put(x, 1);
}
return factors;
}
}
P2660 zzc 种田
解题思路
输入处理:使用
Scanner
读取输入的 x 和 y 值。计算最大公约数:通过欧几里得算法计算 x 和 y 的最大公约数。
公式计算:代入公式 4×(x+y−gcd(x,y))4×(x+y−gcd(x,y)) 得到最小体力值,并输出结果。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
long x = input.nextLong();
long y = input.nextLong();
long gcd = gcd(x, y);
System.out.println(4 * (x + y - gcd));
input.close();
}
private static long gcd(long a, long b) {
while (b != 0) {
long temp = b;
b = a % b;
a = temp;
}
return a;
}
}
P3601 签到题
解题思路
质因数分解与欧拉函数:欧拉函数
表示与 x 互质的数的个数。通过分解 x 的质因数并应用欧拉函数公式
,其中 p 是 x 的质因数,我们可以高效计算
。
筛法生成质数:使用埃拉托斯特尼筛法生成所有小于等于 sqrt(r) 的质数,用于后续处理。
区间处理:对于每个质数 p,处理区间中的每个 p 的倍数,计算对应的欧拉函数值。
剩余质数处理:处理区间中剩余的大质数,确保每个数的欧拉函数正确计算。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
long l = input.nextLong();
long r = input.nextLong();
int len = (int) (r - l + 1);
long[] phi = new long[len];
long[] rem = new long[len];
for (int i = 0; i < len; i++) {
long x = l + i;
phi[i] = x;
rem[i] = x;
}
int maxPrime = (int) Math.sqrt(r) + 1;
boolean[] isPrime = new boolean[maxPrime + 1];
Arrays.fill(isPrime, true); // 假设所有数都是素数
isPrime[0] = isPrime[1] = false; // 0 和 1 不是素数
// 使用埃拉托色尼筛法标记非素数
for (int i = 2; i * i <= maxPrime; i++) {
if (isPrime[i]) {
for (int j = i * i; j <= maxPrime; j += i) {
isPrime[j] = false; // 标记为非素数
}
}
}
List<Integer> primes = new ArrayList<>();
for (int i = 2; i <= maxPrime; i++) {
if (isPrime[i]) {
primes.add(i);
}
}
// 遍历每个素数,对区间 [l, r] 内的数进行处理
for (int p : primes) {
// 找到区间内第一个是 p 的倍数的数
long start = ((l + p - 1) / p) * p;
if (start > r) {
continue; // 如果起始点超出区间,跳过
}
// 遍历区间内所有是 p 的倍数的数
for (long x = start; x <= r; x += p) {
int i = (int) (x - l); // 计算当前数在数组中的索引
if (rem[i] < p) {
continue; // 如果余数小于当前素数,跳过
}
// 如果当前数能被 p 整除,更新 phi 和 rem
if (rem[i] % p == 0) {
phi[i] = phi[i] / p * (p - 1); // 更新欧拉函数值
while (rem[i] % p == 0) {
rem[i] /= p;
}
}
}
}
// 处理区间内剩余的数(大于 sqrt(r) 的素因子)
for (int i = 0; i < len; i++) {
if (rem[i] > 1) { // 如果余数大于 1,说明是一个素数
phi[i] = phi[i] / rem[i] * (rem[i] - 1); // 更新欧拉函数值
}
}
long sum = 0;
long mod = 666623333;
for (int i = 0; i < len; i++) {
long current = ((l + i) - phi[i]) % mod; // 计算当前数的结果
sum = (sum + current) % mod; // 累加结果
}
System.out.println(sum);
input.close();
}
}
P1403 [AHOI2005] 约数研究
解题思路
约数个数的性质:
- 对于一个数
i
,它的约数可以通过遍历1
到sqrt(i)
来找到。- 但是直接计算每个数的约数个数会导致时间复杂度为
,对于 n 最大为 10^6 的情况,这种方法会超时。
优化方法:
- 利用约数的性质,反向思考:对于每个数
j
,它会作为约数出现在j, 2j, 3j, ...
中。- 因此,可以通过遍历每个数
j
,将其作为约数累加到所有的倍数上。- 这种方法的时间复杂度为 O(n),适合 n 较大的情况。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
input.close();
System.out.println(sumOfDivisors(n));
}
// 计算从 1 到 n 的所有正整数的约数个数之和
public static long sumOfDivisors(int n) {
long sum = 0;
for (int i = 1; i <= n; i++) {
sum += (long) (n / i);
}
return sum;
}
}
P1593 因子和
解题思路
质因数分解:
- 将输入的数 a 分解为质因数的乘积形式,例如
。
- 对于每个质因数
,在
中的指数变为
。
等比数列求和:
- 对于每个质因数 p 和其指数 e×b,计算等比数列
的和。
- 使用公式
计算和,但需要注意模运算中的除法处理。
模逆元计算:
- 在模运算中,除法转换为乘以模逆元。利用费马小定理(因为 9901 是质数),模逆元可通过快速幂计算。
特殊情况处理:
- 当 p≡1 mod 9901 时,分母为 0,此时等比数列的和简化为 e+1。
- 处理 a=0 或 b=0 的边界情况。
import java.util.*;
public class Main {
static final int MOD = 9901;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int a = input.nextInt();
int b = input.nextInt();
input.close();
System.out.println(factorSum(a, b));
}
// 计算a^b的因子和,对MOD取模
public static int factorSum(int a, int b) {
if (b == 0) return 1; // a^0 = 1,因子和为1
if (a == 0) return 0; // 0^b = 0(b>0),因子和为0
Map<Integer, Integer> primeFactors = primeFactorize(a);
int result = 1;
for (Map.Entry<Integer, Integer> entry : primeFactors.entrySet()) {
int p = entry.getKey();
int e = entry.getValue() * b;
result = (result * geometricSum(p, e)) % MOD;
}
return (result % MOD + MOD) % MOD; // 确保非负
}
// 质因数分解
public static Map<Integer, Integer> primeFactorize(int n) {
Map<Integer, Integer> factors = new HashMap<>();
// 处理偶数
if (n % 2 == 0) {
int count = 0;
while (n % 2 == 0) {
count++;
n /= 2;
}
factors.put(2, count);
}
// 处理奇数
for (int i = 3; (long) i * i <= n; i += 2) {
if (n % i == 0) {
int count = 0;
while (n % i == 0) {
count++;
n /= i;
}
factors.put(i, count);
}
}
// 处理剩余的质数
if (n > 1) {
factors.put(n, 1);
}
return factors;
}
// 计算等比数列和 (1 + p + p^2 + ... + p^e) % MOD
public static int geometricSum(int p, int e) {
if (e == 0) return 1; // 指数为0时和为1
long pMod = p % MOD;
if (pMod == 1) { // 当p ≡ 1 (mod MOD)时,和为e+1
return (e + 1) % MOD;
}
// 计算 (p^(e+1) - 1) / (p-1) mod MOD
long numerator = (modPow(p, e + 1, MOD) - 1 + MOD) % MOD; // 分子,处理负数
int denominator = modInverse((int) ((p - 1) % MOD), MOD); // 分母的逆元
return (int) ((numerator * denominator) % MOD);
}
// 快速幂计算 (base^exp) % mod
public static int modPow(int base, int exp, int mod) {
long result = 1;
long x = base % mod;
while (exp > 0) {
if ((exp & 1) == 1) {
result = (result * x) % mod;
}
x = (x * x) % mod;
exp >>= 1;
}
return (int) result;
}
// 费马小定理求逆元:a^(mod-2) % mod(要求mod为质数)
public static int modInverse(int a, int mod) {
return modPow(a, mod - 2, mod);
}
}