Acwing第44周周赛
二、最短路径(中等)
题目的第一个条件很好保证,关键在于第二个条件,如何保证从起点到终点,是最短的安全路径(之一)?
考虑下面这种情况:
虚线处代表距离为1,上面这种走的形式肯定不是最优的,因为明明可以走虚线,它非得绕一圈再走过去,这种情况怎么判断?很简单,对于当前点,遍历其四个方向,看周围已经访问过的点的个数是否 > 1,1是因为来的路径肯定会被访问,> 1例如等于2时,就说明当前点可以由其它更近点转移而来,也就代表给出的字符串不是最短路径(之一),输出NO。
import java.util.*;
import java.io.*;
public class Main {
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
String input = reader.readLine();
int x = 200, y = 200;
boolean[][] vis = new boolean[400][400];
vis[x][y] = true;
boolean flag = false;
int[] xx = new int[] {-1,1,0,0};
int[] yy = new int[] {0,0,-1,1};
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) == 'U') {
x -= 1;
} else if (input.charAt(i) == 'D') {
x += 1;
} else if (input.charAt(i) == 'L') {
y -= 1;
} else {
y += 1;
}
if (vis[x][y]) {
flag = true;
writer.write("NO");
break;
}
// 标记已访问
vis[x][y] = true;
int cnt = 0;
for (int j = 0; j < 4; j++) {
int tx = x + xx[j];
int ty = y + yy[j];
if (vis[tx][ty]) cnt++;
}
if (cnt > 1) {
flag = true;
writer.write("NO");
break;
}
}
if (flag == false) writer.write("YES");
writer.flush();
}
}
三、合适数对(困难)
经典数论压轴,看着人就麻。
算术基本定理:任何一个大于1的自然数N,如果N不是质数,那么N可以唯一分解成有限个质数的乘积(如果N是质数,那就只能乘上自己N)。
如果一个数N,是某个数的K次幂,那么它的不同质因数的次数是k的倍数,例如:16 = 2 * 2 * 2 * 2 = 2^4,假定k=2,4是2的倍数,所以16是某个数的K次幂。也就是说我们只用关心当前数的质因数的次数模K的结果。
如果是两个数的乘积,对于公共的质因数,该质因数的次方应该是(a + b),不论公共或是独有的质因数的次数,都应该是K的倍数,例如:4 * 9 = 2 * 2 * 3 * 3 = 2^2 * 3^2,不同的质因数都是k=2的倍数,所以4 * 9可以表示为某个数的平方。
对于每一个数,最多为1e5 = 100000,十万,最多有6个不同的质因数:
2 * 3 * 5 * 7 * 11 * 13 * 17 = 510510
对一个数分解质因子,普通做法:O(根号n),可以用线性筛,线性筛可以知道每个数的最小质因子,而每个数的质因子个数只有logn个,所以只需要O(logn)就可以对某个数进行质因数分解。对于当前数x,求得其最小质因子y,然后再去求x/y的最小质因子即可。
假如一个数N = p1^k1 * p2^k2 * p3^k3,k1、k2都是k的倍数,k3不是,那么,就需要找到另外一个数去把k3凑成k的倍数,那么另外一个数就应该 = power(p3, k - k3),当前数N自己也可以为后面的数贡献p3^k3这个数,方便后面的数使用,同样进行记录。在计算power时,由于k = 200,当k3 = 1时,就算是2的199次方,也是很大的数,所以,如果计算power过程中的值大于数组中可能的最大值,那就直接返回0,代表当前数无意义(不可能找到配对的另一个数)。
import java.util.*;
import java.io.*;
public class Main {
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
static long power(int a, int b) {
long ans = 1;
while (b-- > 0) {
ans *= a;
// 求得的结果大于数组中可能的最大值,无意义
if (ans >= 100010) ans = 0;
}
return ans;
}
public static void main(String[] args) throws IOException {
String[] input = reader.readLine().trim().split(" ");
int n = Integer.parseInt(input[0]);
int k = Integer.parseInt(input[1]);
// 数组模拟哈希表
int[] cnt = new int[100010];
long ans = 0; // 记录对数
input = reader.readLine().trim().split(" ");
int idx = 0;
while (n-- > 0) {
int cur = Integer.parseInt(input[idx++]);
long y = 1, z = 1; // y记录
// 质因数分解,合数最后剩下1
// 如果质因数的次数不是k的倍数,那就用z去补全缺失的次数
for (int i = 2; i * i <= cur; i++) {
if (cur % i == 0) {
int s = 0;
while (cur % i == 0) {
s++;
cur /= i;
}
// 查看当前质因数模k的余数
s %= k;
if (s != 0) {
// 次数不是k的倍数,那就需要另一半凑出k - s的值
y *= power(i, s); // 只记录质因数的次数不是k的倍数的值
z *= power(i, k - s);
}
}
}
// 质数的情况,例如:17
if (cur > 1) {
y *= cur;
z *= power(cur, k - 1);
}
// 如果当前需要的数超过数组中最大数,显然是不可能的
if (z >= 100010) z = 0;
// z是当前数y需要的另一半补全的值
ans += cnt[(int)z];
// y是当前数中质因子不是k的倍数的乘积,从前往后遍历,方便后续数使用
// eg: 24 = 2 * 2 * 2 * 3 = 2^3 * 3^1,如果k = 2,那么y = 2^3 * 3^1
// z = 2 * 3 = 6,也就是说当前y要乘上6才能补成某个数的平方
cnt[(int)y]++;
// 1 1的情况也是满足的,1*1可以表示成1的任意次方
}
writer.write(ans + "");
writer.flush();
}
}