能力全面提升综合题单-练习
Part1
语言基础题
P1089 [NOIP 2004 提高组] 津津的储蓄计划
import java.util.Scanner;
// P1089 [NOIP 2004 提高组] 津津的储蓄计划
public class P1089 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int[] budget = new int[12];
for (int i = 0; i < 12; i++) {
budget[i] = in.nextInt();
}
int money = 0;
int bank_money = 0;
for (int i = 0; i < 12; i++) {
money += 300;
if (money >= budget[i]) {
money -= budget[i];
int save_money = money / 100 * 100;
money -= save_money;
bank_money += save_money;
} else {
System.out.printf("-%d", i + 1);
return;
}
}
System.out.println((int) (bank_money * 1.2 + money));
}
}
关于文件读取:
使用 Reader
的子类 BufferedReader
(读取大文件)
int[] budget = new int[12];
File file = new File("src/input.txt");
try(BufferedReader br = new BufferedReader(new FileReader(file))) {
for (int i = 0; i < 12; i++) {
budget[i]= Integer.parseInt(br.readLine());
}
} catch (Exception e) {
};
for (int i = 0; i < 12; i++) {
System.out.println(budget[i]);
}
P1307 [NOIP 2011 普及组] 数字反转
使用 StringBuilder
的reverse
然后
1.判断正负,负号拿掉
2.反转
3.找到 firstNonZero 第一个非负数字的索引
4.截取,只要后半段
import java.util.Scanner;
import java.util.Stack;
// P1307 [NOIP 2011 普及组] 数字反转
public class P1307 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
StringBuilder sb = new StringBuilder(s);
boolean negative = sb.charAt(0) == '-';
if (negative) {
sb = sb.deleteCharAt(0);
}
sb = sb.reverse();
// 去除前导0
int firstNonZero = 0; // 第一个非零数字的索引
while (sb.charAt(firstNonZero) == '0' && firstNonZero < sb.length()) {
firstNonZero++;
}
String out;
if (negative) {
out = "-" + sb.substring(firstNonZero);
} else {
out = sb.substring(firstNonZero);
}
System.out.println(out);
}
}
数组
P1047 [NOIP 2005 普及组] 校门外的树
正常做法(通过70%)(注意最后结果 +1,因为 0 到 l 有 l+1 个树):
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int l = scanner.nextInt();
int[] map = new int[l];
Arrays.fill(map,1); // 1 tree; 0 not tree
int m = scanner.nextInt();
for (int i = 0; i < m; i++) {
int left = scanner.nextInt();
int right = scanner.nextInt();
for (int j = left; j <= right; j++) {
map[j]=0;
}
}
int res = 0;
for (int i = 0; i < l; i++) {
res+=map[i];
}
System.out.println(res+1);
}
优化(多个区间操作 -> 差分数组)
差分数组 和 前缀和 是一对互逆运算:
- 前缀和:用于快速 计算 某个范围
[L, R]
内的和 - 差分数组:用于快速 修改 某个范围
[L, R]
内的值
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int l = scanner.nextInt();
int[] map = new int[l+1];
int[] d = new int[l+2]; // 差分数组 防止 d[right+1] 溢出
Arrays.fill(map,1); // 1 tree; 0 not tree
int m = scanner.nextInt();
for (int i = 0; i < m; i++) {
int left = scanner.nextInt();
int right = scanner.nextInt();
d[left]-=1;
d[right+1]+=1;
}
// 计算前缀和
map[0] = map[0] + d[0];
for (int i = 1; i <= l; i++) {
map[i]=map[i-1]+d[i];
}
// 统计
int res =0;
for (int i = 0; i <= l; i++) {
if (map[i]>0)
res++;
}
System.out.println(res);
}
P2141 [NOIP 2014 普及组] 珠心算测验
注意读题,是 有多少个数 在前
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
HashSet<Integer> hashSet = new HashSet<>();
int[] nums = new int[n];
for (int i = 0; i < n; i++)
nums[i] = scanner.nextInt();
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
hashSet.add(nums[i] + nums[j]);
}
}
int res = 0;
for (int i = 0; i < n; i++) {
if (hashSet.contains(nums[i])) res++;
}
System.out.println(res);
}
字符串
P1055 [NOIP 2008 普及组] ISBN 号码
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String string = scanner.nextLine(); // 0-670-82162-0
String[] strings = string.split("-"); // 0 670 82162 0
String concat = strings[0].concat(strings[1]).concat(strings[2]); // 0670821620
int sum = 0;
for (int i = 0; i < 9; i++) {
Integer num = concat.charAt(i) - '0'; // 字符数字转为数字
sum += num * (i + 1);
}
sum = sum % 11;
// 校验位 需要判断X
int check = Objects.equals(strings[3], "X") ? 10 : Integer.parseInt(strings[3]);
if (sum == check) {
System.out.println("Right");
} else {
// 拼接
String newString = null;
if (sum == 10) {
newString = strings[0] + "-" + strings[1] + "-" + strings[2] + "-X";
} else {
newString = strings[0] + "-" + strings[1] + "-" + strings[2] + "-" + sum;
}
System.out.println(newString);
}
}
基础算法
模拟
P1003 [NOIP 2011 提高组] 铺地毯
存放所有的地毯坐标 -> 内存超出
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
HashMap<String, Integer> hashMap = new HashMap<>(); // key坐标 value地毯编号
for (int i = 0; i < n; i++) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int g = scanner.nextInt();
int k = scanner.nextInt();
hashMap = updateMap(hashMap, a, b, a + g - 1, b + k - 1, i + 1);
}
int x = scanner.nextInt();
int y = scanner.nextInt();
if (hashMap.containsKey(x + "," + y)) {
System.out.println(hashMap.get(x + "," + y));
} else {
System.out.println(-1);
}
}
private static HashMap<String, Integer> updateMap(HashMap<String, Integer> map, int x, int y, int x2, int y2, int carpet) {
for (int i = x; i <= x2; i++) {
for (int j = y; j <= y2; j++) {
map.put(i + "," + j, carpet);
}
}
return map;
}
不存坐标,而是直接存地毯对象
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
static class Carpet {
int x, y, width, height, id;
Carpet(int x, int y, int width, int height, int id) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.id = id;
}
boolean covers(int px, int py) {
return px >= x && px < x + width && py >= y && py < y + height;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
List<Carpet> carpets = new ArrayList<>();
for (int i = 0; i < n; i++) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int g = scanner.nextInt();
int k = scanner.nextInt();
carpets.add(new Carpet(a, b, g, k, i + 1)); // 存储地毯的范围信息
}
int x = scanner.nextInt();
int y = scanner.nextInt();
// 从后往前遍历,找到最上面的地毯
for (int i = carpets.size() - 1; i >= 0; i--) {
if (carpets.get(i).covers(x, y)) {
System.out.println(carpets.get(i).id);
return;
}
}
System.out.println(-1); // 没有地毯覆盖 (x, y)
}
}
二分答案
二分答案适用:有序 很多可行解中寻求最优解
二分查找的核心思想就是在大量可行解中,通过一个具有单调性(或单调趋势)的判断函数,快速缩小搜索范围,从而找到最优解
例如,在跳石头问题中,我们通过判断函数验证某个候选最小跳跃距离是否可行,再根据判断结果调整搜索区间,最终确定最大的最小跳跃距离。这种方法在很多问题中都适用,只要能证明随着候选值变化,问题的可行性也呈现出单调性趋势,就可以用二分查找来求最优解。
P2678 [NOIP 2015 提高组] 跳石头
import java.util.Arrays;
import java.util.Scanner;
public class P2678_stoneJump {
// 全局变量:l 表示河流总长度,n 表示中间石头数量,m 表示允许移除的石头数量
static int l;
static int n;
static int m;
// 数组 map 用于存放所有石头的位置(包括起点和终点),大小足够大
static int[] map = new int[50001];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读入河流总长度、石头数量(不含起点和终点)以及允许移除的石头数
l = scanner.nextInt();
n = scanner.nextInt();
m = scanner.nextInt();
// 读入中间石头的位置,存入 map 数组,索引从 1 开始
for (int i = 1; i <= n; i++) {
map[i] = scanner.nextInt();
}
// 将起点(位置0)加入数组
map[0] = 0;
// 将终点(位置 l)加入数组
map[n + 1] = l;
// 更新石头总数:原来的 n 个中间石头加上起点和终点
n += 2;
// 如果输入的石头位置没有保证有序,需要对 map 数组进行排序
// Arrays.sort(map, 0, n);
// 二分查找的初始左右边界
int left = 1; // 最小可能的跳跃距离为 1
int right = l; // 最大可能的跳跃距离为河流总长 l
int ans = -1; // 用于记录满足条件的最大最小跳跃距离
// 二分查找过程:当 left <= right 时持续查找
while (left <= right) {
// 计算当前候选的最小跳跃距离 mid,采用防止溢出的方法
int mid = left + (right - left) / 2;
// 调用 judge 函数判断当最小跳跃距离为 mid 时,能否在不超过 m 次移除石头的条件下完成跳跃
if (judge(mid)) {
// 如果可行,则记录 mid 作为当前答案,并尝试更大距离
ans = mid;
left = mid + 1;
} else {
// 如果不可行,则减小候选范围
right = mid - 1;
}
}
// 输出最终得到的最大最小跳跃距离
System.out.println(ans);
}
// judge 函数判断候选最小跳跃距离 x 是否可行
// 贪心思想:从起点开始,若相邻两个保留石头之间距离小于 x,则移除当前石头
// 最后统计需要移除的石头数是否不超过允许的 m 个
private static boolean judge(int x) {
int count = 0; // 用于统计移除的石头数
int now = 0; // now 表示当前保留石头的下标(初始为起点 0)
// 遍历所有石头,从索引 1 开始判断
for (int i = 1; i < n; i++) {
// 如果当前石头与上一个保留石头之间的距离小于候选距离 x,
// 则认为当前石头无法作为跳跃点,需将其移除
if (map[i] - map[now] < x) {
count++;
// 若移除的石头数超过允许的 m,直接返回 false
if(count > m) {
return false;
}
} else {
// 否则,保留当前石头,更新 now 为当前下标
now = i;
}
}
// 判断移除的石头数是否在允许范围内
return count <= m;
}
}
分治
P1226 【模板】快速幂
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long a = scanner.nextLong();
long b = scanner.nextLong();
long p = scanner.nextLong();
System.out.printf("%s^%s mod %s=%s", a, b, p, power(a, b, p));
//2^10 mod 9=7
}
private static long power(long a, long b, long p) {
if (b == 0)
return 1;
long temp = power(a, b / 2, p); // 只计算一次
if (b % 2 == 0)
return temp * temp % p;
else
return (temp * temp % p) * a % p; // 注意溢出,分步取余
}
P3612 [USACO17JAN] Secret Cow Code S
找递推公式,二分(分治)
static long slen;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
long n = scanner.nextLong();
slen = s.length();
long len = s.length();
while (len<=n){
len*=2;
}
System.out.println(s.charAt((int)func(len,n)-1));
}
private static long func(long length,long pos){
// 递推公式:n-1-l/2
if (slen==length)
return pos;
long half = length/2;
if (pos==half+1){
// 后半段的第 1 个字符 -> 前半段的最后 1 个字符
return func(half, half);
}else if (pos<=half){
return func(half,pos);
}else{
return func(half,pos-half-1);
}
}
前缀和 & 差分
前缀和 和 差分数组 是一对互逆运算:
- 前缀和:用于快速 计算 某个范围
[L, R]
内的和 - 差分数组:用于快速 修改 某个范围
[L, R]
内的值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人