【算法练习】Acwing第44周周赛

Acwing第44周周赛

      • 二、最短路径(中等)
      • 三、合适数对(困难)

二、最短路径(中等)

【算法练习】Acwing第44周周赛_第1张图片
【算法练习】Acwing第44周周赛_第2张图片
题目的第一个条件很好保证,关键在于第二个条件,如何保证从起点到终点,是最短的安全路径(之一)?

考虑下面这种情况:
【算法练习】Acwing第44周周赛_第3张图片
虚线处代表距离为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();
    }
}

三、合适数对(困难)

【算法练习】Acwing第44周周赛_第4张图片
经典数论压轴,看着人就麻。

算术基本定理:任何一个大于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
【算法练习】Acwing第44周周赛_第5张图片

对一个数分解质因子,普通做法: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();
    }
}

你可能感兴趣的:(算法练习,算法,数据结构,java)