A. 第几天
题目描述:
题目描述
2000年的1月1日,是那一年的第1天。
那么,2000年的5月4日,是那一年的第几天?输出格式
输出一个整数表示答案
思路解析:
2000年为闰年(366天),判断闰年平年的方式为:能被400整除或者能被4整除不能被100整除。
挨个模拟每一天
代码:
package 第9届;
/**
* @author: DreamCode
* @file: A_第几天.java
* @time: 2022年3月21日-下午9:54:37
* @答案:125
*/
public class A_第几天 {
static int[] arr= {31,29,31,30,31,30,31,31,30,31,30,31};
public static void main(String[] args) {
int ans = 0;
for(int i=0;i<4;i++) { //前4个月的天数
ans+=arr[i];
}
ans+=4;
System.out.println(ans);
}
}
B. 方格计数
题目描述:
题目描述
如图所示,在二维平面上有无数个1x1的小方格。
我们以某个小方格的一个顶点为圆心画一个半径为 1000 的圆。
你能计算出这个圆里有多少个完整的小方格吗?
输出格式
输出一个整数表示答案
思路解析:
考虑到圆的对称性,我们只需要计算第一象限的方格数ans,最终的答案就为ans×4;计算第一象限的方格数时,挨个模拟每个方格数的右上角坐标,根据距离公式判断右上角坐标与圆心的距离是否小于等于R,满足条件则整个方格在圆中。
代码:
package 第9届;
/**
* @author: DreamCode
* @file: B_方格计数.java
* @time: 2022年3月21日-下午9:58:38
* @答案: 3137548
*/
public class B_方格计数 {
public static void main(String[] args) {
int res=0;
int r=1000;
for(int x=1;x<=1000;x++) {
for(int y=1;y<=1000;y++) {
if(distance(x,y)<=r) {
res++;
}
}
}
System.out.println(res*4);
}
private static double distance(int x, int y) {
double d=Math.sqrt(x*x+y*y);
return d;
}
}
C. 复数幂
题目描述:
题目描述
设i为虚数单位。对于任意正整数n,(2+3i)^n 的实部和虚部都是整数。
求 (2+3i)^123456 等于多少? 即(2+3i)的123456次幂,这个数字很大,要求精确表示。
答案写成 “实部±虚部i” 的形式,实部和虚部都是整数(不能用科学计数法表示),中间任何地方都不加空格,实部为正时前面不加正号。
(2+3i)^2 写成: -5+12i,(2+3i)^5 的写成: 122-597i输出格式
按格式输出答案
思路解析:
大整数类的使用,因为结果字符串特别长,所以输出应该指定为文件
代码:
package 第9届;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;
/**
* @author: DreamCode
* @file: B_方格计数.java
* @time: 2022年3月21日-下午9:58:38
*/
public class C_复数幂 {
public static void main(String[] args) throws IOException {
BigInteger a = new BigInteger("2");
BigInteger b = new BigInteger("3");
for (int i = 0; i < 123455; i++) {
BigInteger c = a;
BigInteger d = b;
a = (new BigInteger("2").multiply(c)).subtract(new BigInteger("3").multiply(d));
b = (new BigInteger("2").multiply(d)).add(new BigInteger("3").multiply(c));
}
System.setOut(new PrintStream(new File("src/第9届/C_OutPut.txt")));
System.out.print(a);
if (b.compareTo(BigInteger.ZERO) > 0) {
System.out.print('+');
}
System.out.print(b);
System.out.print("i");
}
}
D. 测试次数
题目描述:
题目描述
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。
塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
某次测试的塔高为1000层,如果我们总是采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?输出格式
输出一个整数表示答案
思路解析:
动态规划法。难点在于如何理解最佳策略与最坏的运气。我们考虑在测试n层的耐摔指数,有k部手机。在每一层模拟摔手机,只有两种结果,第一种为测试当前层p层时摔坏了,则还需要用k-1部手机去测剩下的p-1【0,1,2…p-1】层;第二种为当前层未摔坏,则我们还需要用这k个手机测试剩下的n-p【p+1,p+2…n】层,这里的最坏运气就是指获取两种结果中测试的次数最多的一种结果。最佳策略问题,因为对于测试n层,我们的开始测试的层数选择p可以从1到n,最佳策略,即测试次数最少,所以最佳策略即从1到n开始耐摔获取其中测试次数最少的策略。
因此,状态转移方程:
f(n,k) = min {for i in n: max [ f(i-1,k-1), f(n-i,k) ] }
代码:
package 第9届;
/**
* @author: DreamCode
* @file: D_测试次数.java
* @time: 2022年3月21日-下午11:04:09 @答案:19
*/
public class D_测试次数 {
public static void main(String[] args) {
int n = 1000;
int k = 3;
int ans = DP(k, n);
System.out.println(ans);
}
private static int DP(int k, int n) {
int[][] dp = new int[k + 1][n + 1];
for (int i = 1; i <= n; i++) {
dp[1][i] = i; // 只有一部手机的时候,测试n层肯定是n次
}
for (int i = 2; i <= k; i++) { // 手机数从2-k
for (int j = 1; j <= n; j++) { // 测试层数从1-n
int strategy = Integer.MAX_VALUE; // 记录最佳策略结果
for (int p = 1; p <= j; p++) { // 测试j层有i部手机,模拟分别从1-j层开始摔,找最坏运气,最佳策略结果
int N = dp[i][j - p];// 当前层没摔坏,则需要用i部手机测试剩下的j-p层
int Y = dp[i - 1][p - 1];// 当前层摔坏了,则需要用i-1部手机测试剩下的p-1层
int bad_luck = 1 + Math.max(N, Y); // 最坏的运气
strategy = Math.min(strategy, bad_luck);// 最佳丢手机策略
}
dp[i][j] = strategy;
}
}
return dp[k][n];
}
}
E. 快速排序
代码填空题
package 第9届;
import java.util.Random;
/**
* @author: DreamCode
* @file: E_快速排序.java
* @time: 2022年3月21日-下午11:05:26
*/
public class E_快速排序 {
/**
* 以下代码可以从数组a[]中找出第k小的元素。
*
* 它使用了类似快速排序中的分治算法,期望时间复杂度是O(N)的。
*
* 请仔细阅读分析源码,填写划线部分缺失的内容。
*/
public static int quickSelect(int a[], int l, int r, int k) {
Random rand = new Random();
int p = rand.nextInt(r - l + 1) + l;
int x = a[p];
int tmp = a[p];
a[p] = a[r];
a[r] = tmp;
int i = l, j = r;
while (i < j) {
while (i < j && a[i] < x)
i++;
if (i < j) {
a[j] = a[i];
j--;
}
while (i < j && a[j] > x)
j--;
if (i < j) {
a[i] = a[j];
i++;
}
}
a[i] = x;
p = i;
if (i - l + 1 == k)
return a[i];
if (i - l + 1 < k)
return quickSelect(a,i+1,r,k-i+1); // 填空
else
return quickSelect(a, l, i - 1, k);
}
public static void main(String args[]) {
int[] a = { 1, 4, 2, 8, 5, 7 };
System.out.println(quickSelect(a, 0, 5, 4));
}
}
F. 递增三元组
题目描述:
题目描述
给定三个整数数组
A = [A1, A2, … AN],
B = [B1, B2, … BN],
C = [C1, C2, … CN],
请你统计有多少个三元组(i, j, k) 满足:
\1. 1 <= i, j, k <= N
\2. Ai < Bj < Ck输入格式
第一行包含一个整数N。
第二行包含N个整数A1, A2, … AN。
第三行包含N个整数B1, B2, … BN。
第四行包含N个整数C1, C2, … CN。
1 <= N <= 100000 0 <= Ai, Bi, Ci <= 100000输出格式
一个整数表示答案
输入样例
3 1 1 1 2 2 2 3 3 3
输出样例
27
思路解析:
分别将A、B、C三个数组按照从小到达排序,遍历B数组,每次在A数组中找到最后一个比B[i]小的数的位置p,在C数组中找到第一个比B[i]大的数位置q,当前B[i]可构成的三元组数目为§*(N-q),对每个B[i]累计做和,注意,用 long 存储
package 第9届;
import java.util.Arrays;
import java.util.Scanner;
public class F_递增三元组{
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int N=scanner.nextInt();
int[] A=new int[N];
int[] B=new int[N];
int[] C=new int[N];
for(int i=0;i<N;i++) {
A[i]=scanner.nextInt();
}
for(int i=0;i<N;i++) {
B[i]=scanner.nextInt();
}
for(int i=0;i<N;i++) {
C[i]=scanner.nextInt();
}
Arrays.sort(A);
Arrays.sort(B);
Arrays.sort(C);
long sum=0;
int p=0;
int q=0;
for(int i=0;i<N;i++) {
while(p<N&&A[p]<B[i]) {
p++;
}
while(q<N&&C[q]<=B[i]) {
q++;
}
sum+=(long)(p)*(N-q);
}
System.out.println(sum);
}
}
G. 螺旋折现
试题描述:
题目描述
如图所示的螺旋折线经过平面上所有整点恰好一次。
对于整点(X, Y),我们定义它到原点的距离dis(X, Y)是从原点到(X, Y)的螺旋折线段的长度。
例如dis(0, 1)=3, dis(-2, -1)=9
给出整点坐标(X, Y),你能计算出dis(X, Y)吗?
输入格式
X和Y,数据在int范围以内。
输出格式
输出dis(X, Y)
输入样例
0 1
输出样例
3
思路解析:
对角线的规律问题,分四种象限考虑。画出y=x与y=-x两条线,可以逐步构造出边长为2,4,6…2*k的正方形,k为坐标点的max(abs(x),abs(y)),然后分别讨论四种情况。注意结果需要用long存储
代码:
package 第9届;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: G_螺旋折线.java
* @time: 2022年3月23日-下午8:16:00
*/
public class G_螺旋折线 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
long x = scanner.nextLong();
long y = scanner.nextLong();
long k = Math.max(Math.abs(x), Math.abs(y));
long sum = (8 + 8 * k) * k / 2;
long d = 0;// 计算与当前边长正方形左下角坐标的距离,并将sum减去距离值
if (y == k) { // 上边界
d = k - x + 4 * k;
} else if (x == k) {// 右边界
d = y - (-k) + 2 * k;
} else if (y == -k) {// 下边界
d = x - (-k);
} else if (x == -k) {// 左边界
d = k - y + 6 * k;
}
sum -= d;
System.out.println(sum);
}
}
H. 日志统计
题目描述:
题目描述
小明维护着一个程序员论坛。现在他收集了一份"点赞"日志,日志共有N行。
其中每一行的格式是:ts id。表示在ts时刻编号id的帖子收到一个"赞"。
现在小明想统计有哪些帖子曾经是"热帖"。
如果一个帖子曾在任意一个长度为D的时间段内收到不少于K个赞,小明就认为这个帖子曾是"热帖"。
具体来说,如果存在某个时刻T满足该帖在[T, T+D)这段时间内(注意是左闭右开区间)收到不少于K个赞,该帖就曾是"热帖"。
给定日志,请你帮助小明统计出所有曾是"热帖"的帖子编号。输入格式
第一行包含三个整数N、D和K。
以下N行每行一条日志,包含两个整数ts和id。
1 <= K <= N <= 100000 0 <= ts <= 100000 0 <= id <= 100000输出格式
按从小到大的顺序输出热帖id。每个id一行。
输入样例
7 10 2 0 1 0 10 10 10 10 1 9 1 100 3 100 3
输出样例
1 3
思路解析:
对所有的日志记录按照时间进行排序,按照尺取法遍历所有日志,找出符合要求的id
代码:
package 第9届;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
/**
* @author: DreamCode
* @file: H_日志统计.java
* @time: 2022年3月23日-下午8:56:03
*/
public class H_日志统计 {
static class log {
public int ts;
public int td;
public log(int ts, int td) {
super();
this.ts = ts;
this.td = td;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt();
int D = scanner.nextInt();
int K = scanner.nextInt();
log[] arr = new log[N];
for (int i = 0; i < N; i++) {
arr[i] = new log(scanner.nextInt(), scanner.nextInt());
}
Arrays.sort(arr, new Comparator<log>() { //指定排序规则
@Override
public int compare(log o1, log o2) {
if (o1.ts == o2.ts) {
return o1.td < o2.td ? -1 : 1;
}
return o1.ts < o2.ts ? -1 : 1;
}
});
solve(arr, D, K);
}
private static void solve(log[] arr, int D, int K) {
// TODO 尺取法解决问题
Map<Integer, Integer> rec = new HashMap<>(); // hash表记录每种id的点赞数
Set<Integer> ans = new TreeSet<>(); // 记录热帖的td,默认从小到大排序且去重
int p = 0, q = 0; // 尺取法的双指针
while (q < arr.length) { // 遍历每一种情况
while (arr[q].ts < arr[p].ts + D) {
q++;
if(q==arr.length) {
break;
}
}
for (int i = p; i < q; i++) { // 一次尺,尺子的长度满足[t,t+D);
if (rec.get(arr[i].td) == null) {
rec.put(arr[i].td, 1);
} else {
rec.put(arr[i].td, rec.get(arr[i].td) + 1);
}
}
for (Map.Entry<Integer, Integer> entry : rec.entrySet()) { // 查询当前的尺满足热帖要求的id
if (entry.getValue() >= K) {
ans.add(entry.getKey());
}
}
rec.clear(); //每一次尺取完以后将记录数组清空
p++;
}
Iterator<Integer> iterator=ans.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
I. 全球变暖
题目描述:
题目描述
你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:
....... .##.... .##.... ....##. ..####. ...###. .......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。 由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。 例如上图中的海域未来会变成如下样子:
....... ....... ....... ....... ....#.. ....... .......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入格式
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。输出格式
一个整数表示答案。
输入样例
7 ....... .##.... .##.... ....##. ..####. ...###. .......
输出样例
1
思路解析:
BFS+连通块判断。因为涉及到N<=1000,选择用DFS遍历可能会栈溢出。遍历整个地图,没遇到一个 ‘#’ 并且未访问,则按照此点向四周BFS,用两个计数器cnt1与cnt2分别记录这个岛屿中陆地靠近海洋的数量与不靠近海洋的数量,如果最终cnt1与cnt2相等,则断定该岛屿连通,最终会被淹没。
代码:
package 第9届;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: I_全球变暖.java
* @time: 2022年3月24日-下午10:58:21
*/
public class I_全球变暖 {
static class point {
public int x;
public int y;
public point(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
static int[] x_stride = { -1, 1, 0, 0 }; // 分别代表上下左右移动
static int[] y_stride = { 0, 0, -1, 1 };
static boolean[][] vis; // 记录地图上的该点是否已访问
static char[][] map; // 记录地图上的元素
static int ans = 0; // 记录最终会被淹没的岛屿数量
static int n;
public static void main(String[] args) {
Scanner scanner = new Scanner(new BufferedInputStream(System.in));
n = scanner.nextInt();
map = new char[n][n];
vis = new boolean[n][n];
for (int i = 0; i < n; i++) {
map[i] = scanner.next().toCharArray();
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (map[i][j] == '#' && !vis[i][j]) { // 当前点未访问且当前点未 # ,以该点为中心进行BFS判断是否连通
BFS(i, j);
}
}
}
System.out.println(ans);
}
private static void BFS(int x, int y) {
// TODO BFS遍历
int cnt1 = 0; // 记录靠近海洋的陆地数量
int cnt2 = 0; // 记录陆地的数量
Queue<point> queue = new LinkedList<>(); // 构造队列存储每一个点
queue.offer(new point(x, y));
while (!queue.isEmpty()) {
cnt2++;
point p = queue.peek();
queue.remove();
vis[p.x][p.y] = true; // 标记该点已访问
boolean flag = false; // 默认当前陆地点不靠近海洋
for (int i = 0; i < 4; i++) {// 遍历四个方向
int next_x = p.x + x_stride[i];
int next_y = p.y + y_stride[i];
if (next_x >= 0 && next_x < n && next_y >= 0 && next_y < n) { // 边界判断
if (map[next_x][next_y] == '.') { // 当前的陆地点附近具有海洋,flag设为true
flag = true;
}
if (map[next_x][next_y] == '#' && !vis[next_x][next_y]) {// 附近点具有未访问的陆地,加入队列
queue.offer(new point(next_x, next_y));
}
}
}
if (flag) { // 当前的陆地点附近具有海洋
cnt1++;
}
}
if (cnt1 == cnt2) { // 当前岛屿的每一块陆地都靠海,会被淹没
ans++;
}
}
}
J. 堆的计数
题目描述:
题目描述
我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。
假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?
例如对于N=4有如下3种:
1
/
2 3
/
4
1
/
3 2
/
4
1
/
2 4
/
3
由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。输入格式
一个整数N
输出格式
一个整数表示答案
输入样例
4
输出样例
3
思路解析:
涉及动态规划、快速幂运算、模的逆元、完全二叉树左右孩子的个数;
费马小定理:如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p),即a的逆元为 a!^(p-2)
代码:
package 第9届;
import java.util.Scanner;
/**
* @author: DreamCode
* @file: J_堆的计数.java
* @time: 2022-4-7-22:41:26
*/
public class J_堆的计数 {
private static final int MOD = 1000000009; //质数
private static int N;
private static int[] size; // 记录每个节点的size
private static long[] jie; // 记录1~N的阶乘
private static long[] ni; // 记录1~N的阶乘的逆元
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
size = new int[N + 1];
jie = new long[N + 1];
ni = new long[N + 1];
initSize(); //预处理每一个节点的左右子树节点的个数
initJie(); //预处理阶层与数的逆元
System.out.println(dp());
sc.close();
}
private static long dp() { //动态规划递推
long[] d = new long[N + 1]; // d[i]表示的是i号节点作为根,小根堆的种数
for (int x = N; x >= 1; x--) {
if (2 * x + 1 <= N) {
d[x] = c(size[x] - 1, size[2 * x]) * d[2 * x] % MOD * d[2 * x + 1] % MOD;
} else if (2 * x <= N) {
d[x] = c(size[x] - 1, size[2 * x]) * d[2 * x] % MOD;
} else {
d[x] = 1;
}
}
return d[1];
}
private static void initJie() { //预处理阶乘与模的逆元
jie[0] = 1;
ni[0] = 1;
for (int i = 1; i <= N; i++) {
jie[i] = jie[i - 1] * i % MOD; //递推计算阶乘
ni[i] = qpow(jie[i], MOD - 2); //i的逆元为i!^(MOD-2)
}
}
private static long qpow(long a, int n) { //快速幂运算
if (a == 0) {
return 0;
}
long ans = 1;
long x = a;
while (n > 0) {
if ((n & 1) == 1) {
ans = ans * x % MOD;
}
n >>= 1;
x = x * x % MOD;
}
return ans;
}
private static void initSize() { //初始化每个节点左右子节点的数目。左节点的数目+右节点的数目+1
for (int i = N; i >= 1; i--) {
size[i] = (2 * i <= N ? size[2 * i] : 0) + (2 * i + 1 <= N ? size[2 * i + 1] : 0) + 1;
}
}
private static long c(int n, int r) { //求组合数:n!/r!/(n-r)!
return jie[n] * ni[r] % MOD * ni[n - r] % MOD;
}
}