C代码题解在这里
T1
import java.util.Scanner;
public class Main {
public static void main(String[] args){
// 思路:只杀满足wi>ci/m的怪物,性价比高
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int allC = 0; // 总支出(血量)
int allW = 0; // 总收益(金币):1 金币= m 血量
while(n>0){
int c = sc.nextInt();
int w = sc.nextInt();
if(w*m > c){ // 只有当第i个怪兽的收益血量大于支出血量,才选择打
allW += w;
allC += c;
}
n--;
}
int pure = allC%m == 0? (allW - allC/m) : (allW - allC/m - 1);
System.out.println(pure);
}
}
T2
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 思路:见示意图
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
double a = sc.nextDouble();
double b = sc.nextDouble();
double c = sc.nextDouble();
double delta = 4 * a * a - 8 * a * b * c;
if(delta <= 0){
System.out.println("0");
return;
}
double y1 = (2*a - Math.sqrt(delta)) / (2*b);
double y2 = (2*a + Math.sqrt(delta)) / (2*b);
double s = (a - b*c) / (b*b) * (y2 - y1) - (y2 * y2 * y2 - y1*y1*y1) / (6*a);
System.out.printf("%.10f\n",s);
}
}
T3
算法思路:
直接考虑冲突比较难,可以用容斥原理:总方案 - 不冲突的方案。
总方案:每个犯人都是
m
m
m种编号方案,因此共
n
m
n^{m}
nm种。
不冲突的方案:第一个犯人有
m
m
m种编号方案,之后每一个犯人都与前一个不同因此为
m
−
1
m-1
m−1种编号方案。共
m
∗
(
m
−
1
)
n
−
1
m*(m-1)^{n-1}
m∗(m−1)n−1种。
所以答案就是两者做差,为
n
m
−
m
∗
(
m
−
1
)
n
−
1
n^{m} - m*(m-1)^{n-1}
nm−m∗(m−1)n−1种。
用快速幂+取模做一下。
注意减出来可能是负数,答案=(答案+100003)%100003。
为什么要取余?参考下面快速幂的说法,防止越界报错。
参考网址:
原题来源及解析:BZOJ 1008 [HNOI2008]越狱
快速幂算法(全网最详细地带你从零开始一步一步优化)
import java.util.Scanner;
public class Main {
private static int mod = 100003;
public static int quickMi(int base, int power){
// 快速幂算法,返回base^power
// 这道题而言,反正都要mod 100003,int足够了
int result = 1;
while(power > 0){
if((power&1) == 1) result = result * base % mod; // 此处等价于power%2==1
power >>= 1; // 等价于power /= 2;
base = base * base % mod;
}
return result;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 犯人数
int m = sc.nextInt(); // 编号数/宗教数
int ans = quickMi(m,n) - m * quickMi(m-1,n-1);
if(ans<0) ans = (ans+mod) % mod; // 相减可能为负的情况
System.out.println(ans);
}
}
T4
解题思路:
拿到题的最直观的想法是:将每一个物品
i
i
i与之后的所有物品
j
∈
[
i
+
1
,
)
j \in [i+1,)
j∈[i+1,)的所有属性
k
k
k一一比较,若一致,则加
1
1
1。时间复杂度为
O
(
n
2
k
)
\mathcal{O}(n^{2}k)
O(n2k)。
但想到,可利用HashMap查找降低
n
n
n维到
l
o
g
(
n
)
log(n)
log(n)维,于是有以下构造想法:
要用HashMap还要解决一个问题,若令:
a
i
,
1
+
a
j
,
1
=
a
i
,
2
+
a
j
,
2
=
⋯
=
a
i
,
k
+
a
j
,
k
=
c
a_{i,1}+a_{j,1} = a_{i,2}+a_{j,2}= \dots = a_{i,k}+a_{j,k} = c
ai,1+aj,1=ai,2+aj,2=⋯=ai,k+aj,k=c
c
c
c会随着
i
,
j
i,j
i,j而变化,无法检索到HashMap的key值。因此变换成:
0
=
a
i
,
2
−
a
i
,
1
+
a
j
,
2
−
a
j
,
1
=
⋯
=
a
i
,
k
−
a
i
,
1
+
a
j
,
k
−
a
j
,
1
0 = a_{i,2}-a_{i,1}+a_{j,2}-a_{j,1} = \dots = a_{i,k}-a_{i,1}+a_{j,k}-a_{j,1}
0=ai,2−ai,1+aj,2−aj,1=⋯=ai,k−ai,1+aj,k−aj,1
这样,每次先得到一个
k
−
1
k-1
k−1的特征向量,需要检索的key值是它对应元素的相反数。
另外,检索保持方向一致,具体来说是枚举的逆方向,每次检索key值包含的信息量分别是:
1
,
2
,
3
,
4
,
.
.
.
,
n
−
1
1,2,3,4, ... ,n-1
1,2,3,4,...,n−1
注意的是:java的HashMap除了字符串,HashCode(key值)中存储的都是地址值,例如int[] a = {1,2,3}; int[] b = {1,2,3}
但a.equals(b)-->false
无法比较。对于字符串String a = "123"; String b = "123"; a.equals(b) --> true
。因此对int数组的HashMap查找,需要对int数组进行处理。
这个链接例举了三种如果将byte[]作为HashMap key的处理方法
由于题目说明
2
⩽
k
⩽
10
2 \leqslant k \leqslant 10
2⩽k⩽10,因此我用了int[]转字符串的方法。
import java.util.Scanner;
import java.util.HashMap;
public class Main {
public static String toString(int[] key) {
// 构造一个将int数组转化为String的方法
// 因为HashMap中的Hashcode(key值)如果不是字符串形式,统一存储的是地址值。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < key.length; i++) {
sb.append(key[i]);
}
String str = sb.toString();
return str;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // n个物品
int k = sc.nextInt(); // 每个物品k个属性
HashMap<String, Integer> map = new HashMap<>();
int res = 0; // 记录共有多少对完美对
int[] tmp = new int[k - 1]; // 储存新的k-1个特征
int[] key = new int[k - 1]; // 需查找的key值
String strTmp = "";
String strKey = "";
for (int i = 0; i < n; i++) {// 遍历每一个物品
int a0 = sc.nextInt();
for (int j = 1; j <= k - 1; j++) {
tmp[j-1] = sc.nextInt() - a0; // a_ij - a_i0
key[j-1] = -tmp[j-1];
}
strKey = toString(key);
strTmp = toString(tmp);
if(map.get(strKey)!=null){ // 能查找到key值
res += map.get(strKey);
}
if(map.get(strTmp) == null){ // 如果当前特征没有在map中
map.put(strTmp, 1);
}else{ // 如果在map中,次数num加一,代表这个特征目前已有num+1个了
map.replace(strTmp, map.get(strTmp)+1);
}
}
System.out.println(res);
}
}
这里贴一张以前收藏的String()、StringBuilder()的用法图
T5
思路:并查集。
并查集是一种树的数据结构,主要从路径压缩和按秩合并两个方向优化。这个代码我没有在考试中试过。还没做到这题就时间截止了。
并查集详细讲解链接,up主优秀
import java.util.Scanner;
public class Main {
private static int N = (int) 1e7 + 5;
private static int[] parent = new int[N]; // 该位置对应的父位置(不一定能指向根位置)
private static int[] rank = new int[N]; // 以该位置为根的节点的秩
private static int[] count = new int[N]; // 以该位置为根的树一共有多少个节点
// 初始化关系,自己做自己的根
public static void init() {
for (int i = 0; i < N; i++) {
parent[i] = i;
rank[i] = 1;
count[i] = 1;
}
}
// 找到当前位置的根节点
public static int findRoot(int x) {
if (x == parent[x]) {
return x;
} else {
// 路径压缩,减少下一次查询的时间
parent[x] = findRoot(parent[x]); // 把沿途的父节点都设为根节点
return parent[x];
}
}
// 按秩合并
public static void merge(int n, int m) {
if (rank[n] == rank[m]) {
rank[n]++;
count[n] += count[m];
parent[m] = n;
} else if (rank[n] < rank[m]) { // n指向m
parent[n] = m;
count[m] += count[n];
} else { // rank[m] < rank[n] m指向n
parent[m] = n;
count[n] += count[m];
}
}
public static void main(String[] args) {
// 并查集
Scanner sc = new Scanner(System.in);
int T = sc.nextInt(); // T组测试数据
while (T > 0) {
T--;
init();
int n = sc.nextInt(); // 当前测试数据有n对关系
int preRoot = 0;
int nextRoot = 0;
for (int i = 0; i < n; i++) {
preRoot = findRoot(sc.nextInt()); // 前一个节点对应的根节点
nextRoot = findRoot(sc.nextInt()); // 后一个节点对应的根节点
merge(preRoot, nextRoot);
}
// 遍历count找出以某个位置为根的树的最多节点数
int res = 0;
for (int i = 0; i < N; i++) {
res = Math.max(res, count[i]);
}
System.out.println(res);
}
}
}