CodeRam Algorithm Contest #2 1-6题AC代码及题解
1.BannerDisplay
You’re trying to make several banners that say “coderams” on them, for an upcoming in-person coding competition. However, you have a limited number of letter stickers that you can use. All you have is a long string of n n n lowercase letter stickers. Given this, your task is to figure out the maximum possible number of complete banners (that say “coderams” on them, not just part of the string), given the letter stickers that you have.
Input
The first line of input consists of a single positive integer n ( 1 < = n < = 1 0 5 ) n(1<=n<=10^5) n(1<=n<=105): the number of letter stickers that you have.
The next line consists of a single n n n-character string, each character representing the letter of one sticker that you have. All stickers will have lowercase letters on them.
Output
Output the number of complete banners that you can make with the letter stickers you have.
Scoring
Full problem: 6 points
Example
input |
18 arcmodessmarcodarc |
output |
1 |
input |
4 code |
output |
0 |
input |
32 ramscodecoderamsramscodecoderams |
output |
4 |
题解
此题意为求出所给字符串中的字母可组成字符串“coderams”的数量。例如输入输出样例1中arcmodessmarcodarc中可使用字母“c”、“o”、“d”、“e”、“r”、“a”、“m”、“s”组成一个“coderam”字符串。
理解题意后可以很容易想到使用哈希表分别记录所给字符串中“c”、“o”、“d”、“e”、“r”、“a”、“m”、“s”字母的数量,这八个字母数量的最小数量即为组成字符串“coderams”的上限数量。
AC代码(JAVA)
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int len = input.nextInt();
String letters = input.next();
Solution solute = new Solution();
System.out.println(solute.BannerDisplay(len, letters));
}
}
class Solution {
public int BannerDisplay(int len, String letters) {
int[] freq = new int[26];
Arrays.fill(freq, 0);
for (int i = 0; i < letters.length(); i++) {
freq[letters.charAt(i) - 'a']++;
}
int[] bannerFreq = new int[26];
Arrays.fill(bannerFreq, 0);
String banner = "coderams";
for (int i = 0; i < banner.length(); i++) {
bannerFreq[banner.charAt(i) - 'a']++;
}
int ans = Integer.MAX_VALUE;
for (int i = 0; i < 26; i++) {
if (bannerFreq[i] == 0) continue;
ans = Math.min(ans, freq[i] / bannerFreq[i]);
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
针对此题的优化点
代码中对“coderams”中出现的字母频次也进行了统计,并通过输入字符串与“coderams”的对应字母数量频次相除的最小值得到答案。实际上对于此题不需要特意统计和进行除法操作,输入字符串中八个字母出现数量的最小值即为答案。
2.Array Condensation
You have an array a consisting of n n n positive integers. You decide to “condense” this array. In one operation, you can replace any m m m elements of the array with a single element consisting of their sum.
For example, if you had the array a = [ 2 , 7 , 1 , 8 , 2 , 8 ] a=[2,7,1,8,2,8] a=[2,7,1,8,2,8] and m = 3 m=3 m=3, you could, for example, do the operation on the second, third, and fifth elements, making the array [ 2 , 10 , 8 , 8 ] [2,10,8,8] [2,10,8,8] (note that the order of the array elements doesn’t matter for this problem).
Given this information, your task is to figure out the maximum possible largest element in the array that could exist after k k k operations.
Input
The first line of input contains three space-separated positive integers n n n ( 1 < = n < = 1 0 5 ) (1<=n<=10^5) (1<=n<=105), m m m ( 1 < = m < = n ) (1<=m<=n) (1<=m<=n), and k k k ( 1 < = k < = n , k ∗ m < = n ) (1<=k<=n,k∗m<=n) (1<=k<=n,k∗m<=n): the length of the array, the number of elements that you can change in a single operation, and the number of operations you can make, respectively.
The next line of input contains the array a a a ( 1 < = a i < = 1 0 9 ) (1<=a_i<=10^9) (1<=ai<=109).
Output
Output a single positive integer, representing the maximum possible largest element in the array, after applying exactly k k k of the operations described above.
Note that the answer may overflow traditional 32-bit integers.
Scoring
Full problem: 9 points
Example
input |
6 3 1 2 7 1 8 2 8 |
output |
23 |
input |
9 2 3 1 5 4 9 8 6 3 5 7 |
output |
30 |
题解
输入格式为第一行
n
n
n,
m
m
m,
k
k
k分别表示待输入数组的长度,每次操作合并的元素个数和操作次数。第二行即为数组各元素。
此题意为将数组中的
m
m
m个元素合并为
m
m
m个元素之和,经过
k
k
k次这样的操作后求可以得到的数组中的最大值。考虑贪心算法,每次合并时都将数组中最大的
m
m
m个元素合并,k次操作后得到的总和值即为所求。可以看出,这样的操作相当于求数组中最大的
m
+
(
m
−
1
)
∗
(
k
−
1
)
m+(m-1)*(k-1)
m+(m−1)∗(k−1)个元素的和,所以在实际计算中直接对数组进行排序,然后求数组第
n
−
(
m
+
(
m
−
1
)
∗
(
k
−
1
)
)
=
n
−
k
∗
(
m
−
1
)
−
1
n-(m+(m-1)*(k-1))=n-k*(m-1)-1
n−(m+(m−1)∗(k−1))=n−k∗(m−1)−1个元素到数组最后一个元素的和即为答案。
AC代码(JAVA)
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
int len = input.nextInt();
int changes = input.nextInt();
int operation = input.nextInt();
long[] arr = new long[len];
for (int i = 0; i < len; i++) {
arr[i] = input.nextInt();
}
Solution solute = new Solution();
System.out.println(solute.ArrayCondensation(len, changes, operation, arr));
}
}
class Solution {
public long ArrayCondensation(int len, int changes, int operation, long[] arr) {
Arrays.sort(arr);
long ans = 0;
for (int i = len - 1; i >= 0 && i > len - 2 - (changes - 1) * operation; i--) {
ans += arr[i];
}
return ans;
}
}
3.Taiga Tree
You have a tree, or an undirected connected graph with no cycles, with n n n vertices and n − 1 n−1 n−1 edges. Vertex 1 is the root.
You define a “leaf vertex” to be a vertex on the tree, other than the root, that is adjacent to exactly one branch vertex.
You also define a “branch vertex” to be a vertex on the tree other than the root, that is adjacent to exactly two other vertices, and adjacent to at least one leaf vertex.
You define a tree to be more of a “taiga tree” the more branch vertices that it has. Given a tree, figure out how many branch vertices it has.
Input
The first line of input contains a single positive integer n n n ( 1 < = n < = 1 0 5 ) (1<=n<=10^5) (1<=n<=105): the number of vertices on the tree.
The next n − 1 n−1 n−1 lines each contain two space-separated integers, each representing an edge on the tree.
Output
Output a single positive integer: the number of branch vertices on the tree, as defined above.
Scoring
Full problem: 15 points
input |
6 1 2 2 3 1 4 4 5 5 6 |
output |
2 |
input |
8 1 2 2 3 1 4 4 5 5 6 6 7 6 8 |
output |
1 |
Note
Here is a visual representation of the first example case (the branch vertices are circled in red):
题解
题中定义了“叶子结点”和“分支结点”两个概念。“叶子结点”为只与一个分支结点相邻的除根结点外的结点,“分支结点”为只与两个结点相邻且至少与一个叶子结点相邻的除根结点外的结点。题目要求所给图的“分支结点”个数。
对于图邻接问题可以考虑通过输入边得到邻接矩阵,从邻接矩阵中寻找答案。邻接矩阵是一个存放顶点间关系(边或弧)的数据的二维数组,如输入样例
1
1
1中的图的邻接矩阵(下标从
0
0
0开始)即为
[ 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 ] \left[ \begin{matrix} 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 1 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 \end{matrix} \right] ⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡0000000001010001010000010000010001000001010000010⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
第
i
i
i行第
j
j
j列为1则表示结点
i
i
i与结点
j
j
j相邻,为0则表示这两结点不相邻。
通过邻接矩阵可以很容易得到结点间的相邻关系,从而得到符合题目定义的叶子结点的编号,再通过叶子结点的相邻关系找到符合题目定义的分支结点即可。
例如输入样例
1
1
1,通过邻接矩阵(行列从
0
0
0开始计数)可知第
3
3
3行,第
6
6
6行只有
1
1
1列值为
1
1
1,所以结点
3
3
3和结点
6
6
6都只与一个结点相邻,是符合题目定义的叶子结点。又由邻接矩阵可以得到这两行中值为
1
1
1的列分别为第
2
2
2列和第
5
5
5列,所以结点
3
3
3和结点
6
6
6分别只与结点
2
2
2和结点
5
5
5相邻。再看结点
2
2
2和结点
5
5
5,它们在邻接矩阵中都只与
2
2
2个结点相邻,所以结点
2
2
2和结点
5
5
5都为题目定义的分支结点,故而图中的分支结点个数为
2
2
2。
事实上,本题的结点个数
n
n
n最大为
1
0
5
10^5
105,而构建邻接矩阵需要
O
(
n
2
)
O(n^2)
O(n2)的空间复杂度,也就是说邻接矩阵最大元素个数为
1
0
10
10^{10}
1010个,即使邻接矩阵使用二维布尔数组表示,每个元素也需要
1
B
y
t
e
1Byte
1Byte的空间,那么直接使用邻接矩阵最大将消耗
1
0
10
B
y
t
e
10^{10}Byte
1010Byte的空间。这远远超出了题目所给的
256
M
B
=
2
28
B
y
t
e
256MB=2^{28}Byte
256MB=228Byte的空间限制,所以二维邻接矩阵在提交后会在数据量较大的测试点发生MLE(内存超限)。
考虑对邻接矩阵进行降维,分析使用二维邻接矩阵时候的算法:首先在邻接矩阵中搜索相邻结点个数为
1
1
1的结点,然后通过邻接矩阵找到其相邻结点,最后判断这一相邻结点邻接的结点个数是否为
2
2
2。如果为
2
2
2则该相邻结点为分支结点,结果
+
1
+1
+1,否则则不是。可以看出,邻接矩阵在算法中的作用是:
①寻找叶子结点的相邻结点;
②获取与结点相邻的结点个数。
对于①,由于叶子结点只有一个相邻结点,所以我们只需要一个一维的邻接矩阵记录每个结点的一个相邻结点即可。
对于②,我们只需要使用一个一维数组在遍历边时去统计每个结点相邻结点的个数即可。
这样,我们就将一个二维邻接矩阵降维至两个一维数组,两个数组可以同时在遍历边时进行维护。
AC代码(JAVA):
import java.util.*;
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
int n = input.nextInt();
int[][] edges = new int[n - 1][2];
for (int i = 0; i < n - 1; i++) {
edges[i][0] = input.nextInt();
edges[i][1] = input.nextInt();
}
Solution solute = new Solution();
System.out.println(solute.TaigaTree(n, edges));
}
}
class Solution {
public int TaigaTree(int n, int[][] edges) {
int ans = 0;
int[] adjacency = new int[n + 1];
int[] adjacentCount = new int[n + 1];
Arrays.fill(adjacentCount, 0);
for (int[] edge : edges) {
adjacency[edge[0]] = edge[1];
adjacency[edge[1]] = edge[0];
adjacentCount[edge[0]]++;
adjacentCount[edge[1]]++;
}
for (int i = 2; i < n + 1; i++) {
if (adjacentCount[i] == 1 && adjacency[i] != 1 && adjacentCount[adjacency[i]] == 2) {
ans++;
}
}
return ans;
}
}
4.School Contact Tracing
You go to a school with n n n students, all of which can be identified by a unique student ID from 1 1 1 to n n n, inclusive. The school has m m m classes, each consisting of k k k students.
Unfortunately, c c c students just tested positive for COVID-19, and the school is figuring out which other students need to go into quarantine due to the c c c students testing positive.
Since the school is being very cautious, they decide to have any students in at least one class with the c c c students quarantine, but the school decides to extend the rule and have any students in a class with a quarantined student quarantine as well. This continues in a “chain reaction” process, until there are no more additional students that have to quarantine.
Given the c c c students that just tested positive, figure out the ID’s of any additional students that have to quarantine (not including the original c students).
Input
The first line of input consists of three space-separated integers n n n ( 1 < = n < = 1 0 5 ) (1<=n<=10^5) (1<=n<=105), m m m ( 1 < = m < = 1 0 5 ) (1<=m<=10^5) (1<=m<=105), and k k k ( 1 < = k < = 1 0 5 ) (1<=k<=10^5) (1<=k<=105), m ∗ k < = 1 0 5 m∗k<=10^5 m∗k<=105: the number of students in the school, the number of classes in the school, and the number of students in each class, respectively.
The next m lines each contain k k k distinct space-separated integers: the student ID’s of each student in each class.
The next line contains a single positive integer c c c ( 1 < = c < = n ) (1<=c<=n) (1<=c<=n): the number of students that recently tested positive for COVID-19.
The next line contains c c c distinct space-separated positive integers: the student ID’s of the students that recently tested positive.
Output
Output a single positive integer q q q: the number of additional students that have to quarantine.
Then, output a single line consisting of q q q space-separated integers in ascending order: the number of students that will have to quarantine (not including the students that already tested positive).
Scoring
Subtask 1 (14 points):
(
1
<
=
n
,
m
,
k
,
m
∗
k
<
=
1000
)
(1<=n,m,k,m∗k<=1000)
(1<=n,m,k,m∗k<=1000)
Subtask 2 (9 points): no additional constraints
Examples
input |
13 4 5 1 3 5 6 7 3 5 8 6 1 2 9 11 10 4 4 11 9 13 2 3 1 5 8 |
output |
3 3 6 7 |
input |
13 4 5 1 3 5 6 7 3 5 8 6 1 2 9 11 10 4 4 11 9 13 2 4 1 5 8 4 |
output |
8 2 3 6 7 9 10 11 13 |
input |
30 10 5 18 5 2 20 15 7 18 16 9 17 23 26 15 24 21 8 29 10 23 7 29 17 10 2 21 2 14 20 7 11 21 20 4 15 23 10 18 6 16 9 20 12 11 8 2 18 6 20 16 19 1 29 |
output |
21 2 4 5 6 7 8 9 10 11 12 14 15 16 17 18 19 20 21 23 24 26 |
题解
题意:学生新冠阳性,学校要进行隔离,为了安全起见,被隔离的学生所在班的所有学生都需要被隔离,求除新冠阳性外学生,被隔离的学生人数并输出他们的学生编号。
一、BFS
使用两个一维数组分别记录班级隔离情况和学生隔离情况,一个哈希表记录学生所在的班级,将隔离学生id装入队列,依次遍历所有将被隔离学生并修改隔离情况,避免重复记录。
AC代码(JAVA)
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
int stuNum = input.nextInt();
int classNum = input.nextInt();
int classStuNum = input.nextInt();
int[][] classes = new int[classNum][classStuNum];
for (int i = 0; i < classNum; i++) {
for (int j = 0; j < classStuNum; j++) {
classes[i][j] = input.nextInt();
}
}
int positiveNum = input.nextInt();
int[] positives = new int[positiveNum];
for (int i = 0; i < positiveNum; i++) {
positives[i] = input.nextInt();
}
Solution solute = new Solution();
List<Integer> ans = solute.SchoolContactTracing(stuNum, classNum, classStuNum, classes, positiveNum, positives);
System.out.println(ans.size());
for (int i : ans) {
System.out.print(i);
System.out.print(" ");
}
}
}
class Solution {
public List<Integer> SchoolContactTracing(int stuNum, int classNum, int classStuNum, int[][] classes, int positiveNum, int[] positives) {
List<Integer> ans = new ArrayList<>();
HashMap<Integer, ArrayList<Integer>> stuClass = new HashMap<>(); //studentId-classId map
for (int i = 0; i < classNum; i++) {
for (int id : classes[i]) {
ArrayList<Integer> tmp = stuClass.getOrDefault(id, new ArrayList<>());
tmp.add(i);
stuClass.put(id, tmp);
}
}
int[] stuCondition = new int[stuNum + 1]; //student quanrantined or positive
int[] classCondition = new int[classNum]; //class quanrantined or positive
Arrays.fill(stuCondition, 0);
Arrays.fill(classCondition, 0);
Queue<Integer> q = new ArrayDeque<>();
for (int id : positives) {
stuCondition[id] = 1;
q.add(id);
}
while (!q.isEmpty()) {
int cur = q.poll();
for (int i : stuClass.getOrDefault(cur, new ArrayList<>())) {
if (classCondition[i] == 1) continue;
classCondition[i] = 1;
for (int j : classes[i]) {
if (stuCondition[j] != 1) {
q.add(j);
stuCondition[j] = 1;
ans.add(j);
}
}
}
}
Collections.sort(ans);
return ans;
}
}
二、并查集
可以使用并查集将同班学生合并,最后寻找存在阳性学生的集合,集合内除阳性学生外全部加入到隔离数组中即可。
AC代码(JAVA)
import java.util.*;
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
int stuNum = input.nextInt();
int classNum = input.nextInt();
int classStuNum = input.nextInt();
int[][] classes = new int[classNum][classStuNum];
for (int i = 0; i < classNum; i++) {
for (int j = 0; j < classStuNum; j++) {
classes[i][j] = input.nextInt();
}
}
int positiveNum = input.nextInt();
int[] positives = new int[positiveNum];
for (int i = 0; i < positiveNum; i++) {
positives[i] = input.nextInt();
}
Solution solute = new Solution();
List<Integer> ans = solute.SchoolContactTracing(stuNum, classNum, classStuNum, classes, positiveNum, positives);
System.out.println(ans.size());
for (int i : ans) {
System.out.print(i);
System.out.print(" ");
}
}
}
//并查集
class UnionFind {
int[] f;
int[] rank;
int _size;
UnionFind(int size) {
this._size = size;
f = new int[_size];
rank = new int[_size];
for (int i = 0; i < _size; i++) {
f[i] = i;
}
}
public int find(int n) {
if (f[n] != n) {
f[n] = find(f[n]);
}
return f[n];
}
public void merge(int i, int j) {
int x = find(i), y = find(j);
if (rank[x] < rank[y]) {
int tmp = x;
x = y;
y = tmp;
}
rank[x] += rank[y];
f[y] = x;
}
}
class Solution {
public List<Integer> SchoolContactTracing(int stuNum, int classNum, int classStuNum, int[][] classes, int positiveNum, int[] positives) {
UnionFind uf = new UnionFind(stuNum + 1);
for (int i = 0; i < classNum; i++) {
for (int j = 1; j < classStuNum; j++) {
uf.merge(classes[i][j - 1], classes[i][j]);
}
}
int[] stuPositive = new int[stuNum + 1];
int[] classCondition = new int[stuNum + 1];
for (int stuId : positives) {
stuPositive[stuId] = 1;
classCondition[uf.find(stuId)] = 1;
}
List<Integer> ans = new ArrayList<>();
for (int i = 1; i <= stuNum; i++) {
if (stuPositive[i] != 1 && classCondition[uf.find(i)] == 1) {
ans.add(i);
}
}
return ans;
}
}
5.Magic Numbers
You consider a number to be “magic” if it has an even number of digits (not counting leading zeros), and if the first half of the number is equal to the second half of the number. For example, 13151315 13151315 13151315, 878878 878878 878878, and 9999 9999 9999 are magic numbers, while 35353 35353 35353, 12345 12345 12345, and 3663 3663 3663 are not.
You really like magic numbers, and you decide to play a game given a starting number n n n.
In one turn, you change the number n n n depending on the following instructions:
If n consists entirely of an odd number of nines (i.e.
99999
99999
99999 or
9
9
9), the game ends.
Otherwise, if
n
n
n is a magic number (as described above), you replace
n
n
n by the first half of its digits (so
13151315
13151315
13151315 would become
1315
1315
1315), which takes one turn.
Otherwise, you increase
n
n
n by
1
1
1, which takes one turn.
It can be proven that the game will always end eventually.
Given these rules, figure out how many turns the game will take before the game ends.
Input
The only line of input contains a single positive integer n n n ( 1 < = n < 1 0 12 ) (1<=n<10^{12}) (1<=n<1012): the number you start out with.
Output
Output a single positive integer t t t: the number of turns you will end up taking before the game ends.
Scoring
Subtask 1 (6 points):
n
<
=
1
0
5
n <= 10^5
n<=105
Subtask 2 (18 points): no additional constraints
Examples
input |
86 |
output |
4 |
input |
7977 |
output |
14 |
input |
982490834438 |
output |
148563 |
题解
题意:定义“幻数”为偶数个数字组成的前半数和后半数相等的数字。如13151315、8787等。现在进行一个游戏,从输入数字
n
n
n开始,如果当前数字由奇数个九组成,如99999、9等,则游戏结束;如果当前数字为幻数,则经过变换后只取一半的数字,如13151315将变为1315;如果当前数字不是幻数且不由奇数个九组成则自增1。每次变换都记为一回合,问需要多少个回合后游戏结束。
由题可知处理情况分三种:
①数字由奇数个9组成;
②数字由偶数个数组成且前半部分数与后半部分数相等;
③数字不符合以上两种情况。
可以看到三种情况中讨论最多的是数字位数的奇偶性,所以从这一点出发进行分类讨论。
当当前数字位数为奇数时,可以肯定这一数字必然不是幻数,所以只讨论情况①和情况③。情况①下游戏结束,回合数统计结束。情况③下该数只能通过自增加一进行变换,直到数字满足情况③到达情况①或情况②才结束自增,所以自增结束后的数字与自增起始的数字之差即为这中间经历的回合数。由于当前数字位数为奇数,所以比起从位数为奇数自增进位到位数为偶数的幻数(比起到达情况②),直接自增到与当前数字位数相同的只由9组成的数字(直接到达情况①)中间的差值要更小,所经历的回合数也越少。故而当当前数字位数为奇数且处于情况③时,会优先到达情况①并结束游戏。
当当前数字位数为偶数时,可知显然不符合情况①,只讨论情况②和情况③。情况③下比起从位数为偶数自增进位到位数为奇数的情况①,直接自增到比当前数字大的最小幻数要更快,所以情况③会优先到达情况②。情况②下,直接进行取半操作,对得到的数字再作情况分析。
AC代码(JAVA)
import java.util.*;
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
long num = input.nextLong();
Solution solute = new Solution();
System.out.println(solute.magicNumber(num));
}
}
class Solution {
public long magicNumber(long num) {
long ans = 0;
long n = num;
while (true) {
String numStr = String.valueOf(n);
if ((numStr.length() & 1) == 1) {
long endNum = 0;
for (int i = 0; i < numStr.length(); i++) {
endNum = endNum * 10 + 9;
}
ans += endNum - n;
break;
}
else {
long magicNum = findMinMagicNumber(numStr);
ans += magicNum - n + 1;
for (int i = 0; i < numStr.length() / 2; i++) {
magicNum /= 10;
}
n = magicNum;
}
}
return ans;
}
long findMinMagicNumber(String numStr) {
String pre = numStr.substring(0, numStr.length() / 2);
String post = numStr.substring(numStr.length() / 2);
long preNum = Long.parseLong(pre), postNum = Long.parseLong(post);
if (preNum < postNum) {
preNum++;
}
postNum = preNum;
for (int i = 0; i < post.length(); i++) {
preNum *= 10;
}
return preNum + postNum;
}
}
6.Favorite Product
You have an array a consisting of n integers. Your favorite number is k k k, and you want to see if you can find any triple of distinct indices with a product of k k k. In other words, you’re looking for three distinct indices x x x, y y y, and z z z, such that a x ∗ a y ∗ a z = k a_x∗a_y∗a_z=k ax∗ay∗az=k.
Input
The first line of input contains two space separated integers n n n ( 1 < = n < = 1 0 5 ) (1<=n<=10^5) (1<=n<=105) and k k k ( 1 < = k < = 1 0 5 ) (1<=k<=10^5) (1<=k<=105).
The next line consists of n n n space-separated integers: the array a a a ( 1 < = a i < = 1 0 4 ) (1<=a_i<=10^4) (1<=ai<=104).
Output
Output a single positive integer representing the number of triples of distinct indices in a a a that have a product of k k k.
Scoring
Subtask 1 (4 points):
n
<
=
100
n<=100
n<=100
Subtask 2 (8 points):
n
<
=
2000
n<=2000
n<=2000
Subtask 3 (26 points): no additional constraints
Example
input |
5 24 6 2 3 4 1 |
output |
2 |
input |
7 7 1 1 1 7 7 7 8 |
output |
9 |
input |
6 27 1 3 3 3 3 9 |
output |
8 |
题解
题意:给定一个目标数,统计数组中有多少三元组满足乘积等于目标数。
由题可知,输入数组可能包含重复数字,所以统计结果可能包含重复三元组。当三元组中的数字在数组中存在重复时,将会导致这个三元组有多种不同的数组下标的组成可能。所以每个三元组重复的可能数与其中的在数组中存在重复的数字有关。
设
c
o
u
n
t
[
x
]
count[x]
count[x] 为数组中
x
x
x 出现的次数。对于每种
x
∗
y
∗
z
=
=
t
a
r
g
e
t
x*y*z == target
x∗y∗z==target,可以数一下有多少种可能的组合,显然这是个排列组合问题。这里可以看几个例子:
①如果
x
x
x,
y
y
y,
z
z
z 各不相同,有
c
o
u
n
t
[
x
]
∗
c
o
u
n
t
[
y
]
∗
c
o
u
n
t
[
z
]
count[x] * count[y] * count[z]
count[x]∗count[y]∗count[z] 种组合。
②如果
x
=
=
y
!
=
z
x == y != z
x==y!=z,有
C
c
o
u
n
t
[
x
]
2
∗
c
o
u
n
t
[
z
]
C_{count[x]}^2 * count[z]
Ccount[x]2∗count[z] 种组合。
③如果
x
!
=
y
=
=
z
x != y == z
x!=y==z,有
c
o
u
n
t
[
x
]
∗
C
c
o
u
n
t
[
y
]
2
count[x] * C_{count[y]}^2
count[x]∗Ccount[y]2 种组合。
④如果
x
=
=
y
=
=
z
x == y == z
x==y==z,有
C
c
o
u
n
t
[
x
]
3
C_{count[x]}^3
Ccount[x]3 种组合。
我们只需要枚举去重后的数组中的每个数字确定三元组中的第一个数,然后用双指针在去重数组中找出另外两个数组成三元组,最后根据以上求组合数的方法求出这一三元组出现的个数。
在枚举三元组中的第一个元素时可以进行一个简单的剪枝。因为数组中元素都为整数,所以
t
a
r
g
e
t
target
target 应当能被第一个元素整除。
import java.util.*;
public class Main {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
int len = input.nextInt();
int favor = input.nextInt();
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = input.nextInt();
}
Solution solute = new Solution();
System.out.println(solute.favoriteProduct(arr, len, favor));
}
}
//AC
class Solution {
public long favoriteProduct(int[] arr, int len, int favor) {
long[] count = new long[10001];
int uniq = 0;
for (int i : arr) {
count[i]++;
if (count[i] == 1) {
uniq++;
}
}
int[] keys = new int[uniq];
int t = 0;
for (int i = 1; i < 10001; i++) {
if (count[i] > 0) {
keys[t++] = i;
}
}
long ans = 0;
for (int i = 0; i < keys.length; i++) {
int x = keys[i];
if (favor % x != 0) continue;
int T = favor / x;
int left = i, right = keys.length - 1;
while (left <= right) {
int y = keys[left], z = keys[right];
if (y * z > T) right--;
else if (y * z < T) left++;
else {
if (i < left && left < right) {
ans += count[x] * count[y] * count[z];
}
else if (i == left && left < right) {
ans += count[x] * (count[x] - 1) / 2 * count[z];
}
else if (i < left && left == right) {
ans += count[x] * count[y] * (count[y] - 1) / 2;
}
else {
ans += count[x] * (count[x] - 1) * (count[x] - 2) / 6;
}
left++;
right--;
}
}
}
return ans;
}
}