AcWing Blue Bridge Cup Group AB Tutorial 08, Number Theory

foreword

Some time ago, in order to be able to deal with some algorithm questions in the interview, I embarked on the road of brushing the questions. Most of them are done on the Likou platform. The current score is 400+. In addition, after arriving at the new school, I learned that the school also organizes the Blue Bridge Cup. Related programming competitions, I plan to try again, I want to learn the algorithm systematically (before that, the main back-end project was the main project, and I didn’t know much about the algorithm after a short period of time), and it was the first time I visited the acwing platform some time ago. I feel that the courses above are relatively systematic, and there are a lot of questions on the platform, so I plan to follow the courses of acwing for a while, and let's work together!

  • Currently I plan to join the Java group, so all the solutions are Java.

All blog file directory index: blog directory index (continuously updated)

A list of greedy exercises in this chapter: Links to Java solutions for all topics

Lecture 8 Study Period: 2023.1.20-2023.1.27

image-20230127135833702

example:

exercise:

1. Number theory

example

Example 1: AcWing 1246. Arithmetic sequence (greatest common divisor, C++B question 7 of the 10th Lanqiao Cup Provincial Competition)

analyze

The amount of data is 100,000, and the time complexity is O(n.logn), O(n).

First, the arithmetic progression is as follows:

x  x+d  x+2d  x+3d ... x+n*d

It is stated in the title that only a small part of this set of arithmetic progressions is given, and you are asked to find the shortest number of arithmetic progressions. At this time, we need to use the numbers of the given series of numbers to find the corresponding d value, how to find the value of d?

We can observe that each number in the arithmetic sequence is composed of a fixed value x and a multiple of the corresponding d, then we can think of finding d by using the greatest common divisor.

Example: We use x+3d - x = 3d, x + 3d - (x - d) = 2d, let 2d and 3d come and go to calculate the greatest common divisor, and then d can be obtained.

How do we get their numbers for the whole arithmetic sequence?

Quantity = (Max - Min) / d + 1

For special cases, for example: 1 1 1, that is, when d is 0, its number should be n.

Then we can go to AC!

Problem Solution: Greatest Common Divisor

Complexity analysis: time complexity O(n.logn); space complexity O(n)

import java.io.*;
import java.util.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final int N = 100010;
    static int n;
    static int[] a = new int[N];
    
    public static void main(String[] args) throws Exception{
    
    
        n = Integer.parseInt(cin.readLine());
        String[] ss = cin.readLine().split(" ");
        for (int i = 0; i < n; i++) {
    
    
            a[i] = Integer.parseInt(ss[i]);
        }
        //排序
        Arrays.sort(a, 0, n);
        //0可以作为起点来进行与之后的数字进行公约数计算
        int d = 0;
        for (int i = 0; i < n; i ++ ) {
    
    
            //a[i] - a[0]只留下对应的n * d,然后求取最大公约数
            d = gcd(d, a[i] - a[0]);
        }
        //若是最大公约数为0,直接返回整个个数
        //举例:0 0 0 0 0
        if (d == 0) {
    
    
            System.out.println(n);
        }else {
    
    
            //等差数列依次为:x  x+d  x+2d  x+3d,(最后一个-第一个) / d + 1 = 3 + 1 = 4
            System.out.println((a[n - 1] - a[0]) / d + 1);
        }
    }
    
    //计算最大公约数
    public static int gcd(int a, int b) {
    
    
        return b == 0 ? a : gcd(b, a % b);
    }
}

image-20230120113346901

In fact, we can optimize, because the purpose of sorting is only to find the minimum value and maximum value, we only need to use O(n) to find it:

import java.io.*;
import java.util.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final int N = 100010;
    static int n;
    static int[] a = new int[N];
    
    public static void main(String[] args) throws Exception{
    
    
        n = Integer.parseInt(cin.readLine());
        String[] ss = cin.readLine().split(" ");
        for (int i = 0; i < n; i++) {
    
    
            a[i] = Integer.parseInt(ss[i]);
        }
        //找到最小值,最大值
        int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
        for (int i = 0; i < n; i ++) {
    
    
            min = Math.min(a[i], min);
            max = Math.max(a[i], max);
        }
        int d = 0;
        for (int i = 0; i < n; i ++ ) {
    
    
            //减去最小值
            d = gcd(d, a[i] - min);
        }
        if (d == 0) {
    
    
            System.out.println(n);
        }else {
    
    
            //最大值-最小值
            System.out.println((max - min) / d + 1);
        }
    }
    
    public static int gcd(int a, int b) {
    
    
        return b == 0 ? a : gcd(b, a % b);
    }
}

image-20230120113519570


Example 2: AcWing 1295. Factor Chain of X (Fundamental Theorem of Arithmetic, Euler's Sieve, Multiple Set Permutations)

Topic link : 1295. Factor chain of X

analyze

The N size of this question is 1 million, and the time complexity should be controlled in O(n.logn), O(n).

This question will be analyzed around the values ​​​​of our two solutions.

Find the maximum length of a sequence :

Here we need to learn the Fundamental Theorem of Arithmetic , which states: Any natural number N greater than 1, if N is not a prime number, then N can be uniquely decomposed into the product of a finite number of prime numbers. All integers can be uniquely decomposed into the form of the product of several prime factors.

  • The product formula is: N=P1 a1 P2 a2 P3 a3 * ... Pn an , where P1<P2<P3...<Pn are all prime numbers, and the exponent ai is a positive integer. Such a decomposition is called the standard decomposition of N. The earliest proof was given by Euclid.

According to the title description requirements of this question: the factors of X greater than 1 form a strictly increasing sequence that satisfies any previous item that can divisible the next item.

举例严格递增情况:2    2*2    2*2*3   2*2*3*4

The description of the topic is perfectly aligned with the fundamental theorem of arithmetic: N=P1 a1 P2 a2 P3 a3 * ... Pn an , we resolve a number according to the fundamental theorem of arithmetic and then we can get the maximum length of the sequence as a1 + a2 + a3 + a4 + ... + an .

why? for example:

180 = 22 * 32 * 5

a1 = 2,a2 = 2,a3 = 1此时能够构成序列最大长度为5
//注意:题目中意思是去让你从对应因子组成中的数进行选择,例如2*2,你可以选择2以及4加入到这个序列当中去,不是说选了一个2,就只能再选一个2
//组成序列的如下,就是5
2   2*2   2*2*3   2*2*3*3   2*2*3*3*5

It is precisely because we need to use the formula of the Fundamental Theorem of Arithmetic that we need to use Euler's sieve method to find all the prime numbers in 1 ~ n and screen out the smallest prime factor of each number . (The amount of data in this question is 1 million, and the Euler sieve method O(n) complexity)

For the idea and detailed code of the Euler screening method (including naive screening, Egyptian screening and Euler screening): Euler screening method of number theory (including simple screening, Egyptian screening detailed code)

It is very important to screen out the smallest prime factor of each number, which can be used to deduce the corresponding formula of the fundamental theorem of arithmetic. For example, 180 can deduce that the smallest prime factor (prime number) is 2, and then 180/4=45, the smallest prime of 45 The factor is 3, 45/9=5, the smallest prime factor of 5 is 5, and the final composition: 180 = 2 2 * 3 2 * 5. At this point, we can get the corresponding a1, a2, a3...an, and then we can find the maximum length of the sequence.

The number of sequences that satisfy the maximum length : in fact, the number of sequences of the maximum length is fully arranged and deduplicated

The general proof theorem of Y: first do a mapping, not the number itself, but the increment of the number, the original sequence is a1, a2, a3...an, the mapped sequence is a1, a 2 a 1 a2\ over a1a 1a 2, a 3 a 2 a3\over a2 a 2a 3 a n a n − 1 an\over an-1 an1an, these two sequences are corresponding, we can find the second sequence by giving us the first sequence, and every number in the second sequence is also a prime factor, so the number of sequences is the full arrangement of all prime factors number (need to remove duplicate numbers).

Can we also do multiple schemes for sequences of maximum length? Let's use a practical example to illustrate the above proof theorem: 180 = 2 2 * 3 2 * 5

2   2*2   2*2*3   2*2*3*3   2*2*3*3*5

//实际上我们还可以从3开始,同样能够构成最大长度序列
3   3*2   3*2*2   3*2*2*3   3*2*2*3*5

So in fact, we are seeking the number of the maximum length sequence to arrange the columns (removing the full arrangement of the same elements)

The corresponding multiset permutations are as follows:

N elements are fully arranged: N!

Remove the same elements as the following example:

3 3 3,相互可交换的次数为2*3,实际上就是ai!,ai指的是对应3的个数

The final full permutation with identical elements removed is: ( a 1 + a 2 + a 3 + . . . + an ) ! a 1 ! a 2 ! . . . an ! (a1 + a2 + a3 + ... + an )!\over a1!a2!...an!a 1 ! a 2 ! ... an !( a 1 + a 2 + a 3 + ... + an ) !

Solution: Number Theory - Fundamental Theorem of Arithmetic, Euler's Sieve, Permutations of Multiple Sets

Complexity analysis: time complexity O(n); space complexity O(n)

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    //2的20次方为1,048,576
    static final int N = 1100010;
    //存储合数
    static int[] coms = new int[N];
    //存储质数
    static int[] primes = new int[N];
    //存储质数的数量
    static int primeCount = 0;
    //存储所有数的最小质因子
    static int[] minPrimes = new int[N];
    static int x;
    
    //欧拉筛选
    public static void getPrimes(int n) {
    
    
        //遍历所有的数字
        for (int i = 2; i <= n; i++) {
    
    
            if (coms[i] == 0) {
    
    
                primes[primeCount++] = i;
                //存储质数的最小质因子,当前就是本身
                minPrimes[i] = i;
            }
            //遍历质数数组
            for (int j = 0; primes[j] * i <= n && j < primeCount; j++ ) {
    
    
                int t = primes[j] * i;//合数值
                //建立合数
                coms[t] = 1;
                //构建合数的最小质因子
                minPrimes[t] = primes[j];
                //若是当前i能够整除primes数组中的质数,此时直接提前结束
                if (i % primes[j] == 0) break;
            }
        }
    }
    
    public static void main(String[] args) throws Exception{
    
    
        //提前进行欧拉筛选
        getPrimes(N - 1);
        
        //来进行多轮操作
        while (true) {
    
    
            //读取两个数组
            String line = cin.readLine();
            if (line == null || line.length() == 0) break;
            //获取到数字
            int n = Integer.parseInt(line);
            //构建n = ...,基本的算数定理公式
            //使用fact、sum来分别记录N的质因子以及质因子的个数
            int[] fact = new int[100], sum = new int[100]; 
            //记录当前n的质因子的个数,对应fact的下标
            int k = 0;
            //记录质因子总数(题解1:序列的最大长度)
            int total = 0;
            //尝试去分解n的质因数
            while (n > 1) {
    
    
                //获取到当前n的最小质因子
                int minPrime = minPrimes[n];
                //设置第k个质因子为当前n的最小质因子
                fact[k] = minPrime;
                //对应第k个质因子数量从0开始计算
                sum[k] = 0;
                //为false情况:一旦不能够整除,此时就该要去找后一个不同的质因子
                while (n % minPrime == 0) {
    
    
                    //当前质因子数量+1
                    sum[k]++;
                    //质因子总数+1
                    total++;
                    n /= minPrime;
                }
                k++;
            }
            
            //开始计算(题解2:满足最大长度的序列的个数)
            long res = 1;
            //首先计算质因子总数的全排列方案数
            for (int i = 1; i <= total; i++) {
    
    
                res *= i;
            }
            //接着去除重复的情况
            //遍历所有的质因子
            for (int i = 0; i < k; i ++) {
    
    
                //每个质因子的阶乘(使用res继续除以)
                for (int j = 1; j <= sum[i]; j++) {
    
    
                    res /= j;
                }
            }
            
            //输出
            System.out.printf("%d %s\n", total, String.valueOf(res));
        }
        
    }
}

image-20230120180033030


Example 3: AcWing 1296. Smart Stefanie

Essence: violent search + pruning

Study articles: AcWing 1296. Smart Stefanie—Detailed Solution , AcWing 1296. Smart Stefanie (Lanqiao Cup C++ AB group tutorial)—Video

analyze

First of all, let's understand the meaning of the question : it is to give you the sum of the divisors of a number, and let you find multiple numbers that meet this condition (there may be more than one).

  • Example: The sum of divisors is 42, and the sum of the constraints of numbers 20, 26, and 41 is 42.
  • The divisors of the number 20 are: 1 2 4 5 10 20, the sum of which is 42.

The idea of ​​violent enumeration in this question is : enumerate the divisors of all numbers from 1 to S-1 for a number S, and then judge whether the sum of their divisors is equal to S. But since the maximum value of S is 2 billion, it will definitely time out.

At this point, the formula for the sum of divisors can be used : S = (1+p1+p1 2 +…+p1 a1 )(1+p2+p2 2 +…+p2 a2 )…(1+pn+pn 2 +… +pn an )

If there is a divisor s that satisfies the above formula, then 3x5x9x17x33x65x129 = 635037975, which is relatively close to the complexity of O(n), that is, the maximum value of S for this question is 2 billion.

For this process, dfs is used to search, and two layers of for loops are used to enumerate:

for(p : 2,3,5,7,...)
    for(a : 1,2,3,...)
        if(S mod (1+p1+p1^2+...+p1^a1) == 0)
            dfs(下一层)
//dfs(质数下标开始位置,上一层的实际答案结果,s剩余待整除的值)

The following gives the three result values ​​20, 26, and 41 whose sum of divisors is 42. You can see the result values ​​obtained by using the sum of divisors formula:

①20:1 2 4 5 10 20

  • 20 = 22 * 5 = (20+ 21 + 22)*(50+51)=42

②26:1 2 13 26

  • 26 = 2 * 13 = (20 + 21) * (130 + 131) = 42

③41:1 41

  • 41 = 41 = (410 + 411) = 42

How to use pruning to reduce some calculations?

  • We can find that the actual formula is actually composed of () * (), so our actual boundary range can be sqrt(s), for this case, we need to consider the special case that is S = (1 + pi).
  • For example: if the sum of divisors is 42, since the boundary is sqrt(s), the largest prime number traversed at this time is 7, and one result number is 41. In this case, when the boundary cannot be reached, we only Need to consider whether the pi in S = (1 + pi) is a prime number,

In addition, this question needs to screen prime numbers, so Euler screening needs to be used to achieve O(n) complexity. For simple screening, Ella screening, and Euler screening ideas and codes, you can see the blog: Euler screening method of number theory (including simple screening , Angstrom filter code for details) .

Problem solution: Euler sieve + sum of divisors (dfs) + pruning

DFS code without sqrt(): 2 billion array spaces need to be opened, and this question cannot be ac

public static void dfs(int last, int pro, int s) {
    
    
    if (s == 1) {
    
    
        res[len++] = pro;
        return;
    }
    //你可以看到这里primes[i] <= s对于这种情况也依旧是可以实现的,但是对于本题S为20亿则会直接超时
    //优化点:对于primes[i] <= s / primes[i]则可直接对(s - 1)来判断进行优化。
    for (int i = last + 1; primes[i] <= s; i++) {
    
    
        int p = primes[i];
        for (int j = 1 + p; j <= s; p *= primes[i], j += p) {
    
    
            if (s % j == 0) {
    
    
                dfs (i, pro * p, s / j);
            }
        }
    }
}

Add pruning optimization: traverse sqrt(s), add the judgment condition of s-1 situation

public static void dfs(int last, int pro, int s) {
    
    
    if (s == 1) {
    
    
        res[len++] = pro;
        return;
    }
    //剪枝(优化):提前判断当前(s-1)是否是一个质数,若是(s-1)>上一个质数 && (s-1)是一个质数(主要也是解决数据量过大的问题)
    //对应下方for循环遍历的终点是:primes[i] <= s / primes[i]
    //举例:对于值为41,其约数为1、41,走下面的for循环(若是原本primes[i] <= s / primes[i])时,实际上只会遍历到最大质数为7就无法往后了,所以这边来进行提前剪枝操作
    int pre = last >= 0 ? primes[last] : 1;
    if (s - 1 > pre && isPrime(s - 1)) {
    
    
        res[len++] = pro * (s - 1);
    }
    for (int i = last + 1; primes[i] <= s / primes[i]; i++) {
    
    
        int p = primes[i];
        for (int j = 1 + p; j <= s; p *= primes[i], j += p) {
    
    
            if (s % j == 0) {
    
    
                dfs (i, pro * p, s / j);
            }
        }
    }
}

Note: If you use java, if you use System.out.print directly, it will time out. It is recommended to use BufferedReader and PrintWriter

Full code:

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
    static final int N = 500000;//特判S = 1+p,最大S = p*p
    
    //欧拉筛所需要数组
    //flag表示合数数组,true为合数
    static boolean[] flag = new boolean[N];
    //存储质数
    static int[] primes = new int[N];
    static int cnt = 0;
    
    //存储每一组数据的答案
    static int[] res = new int[N];
    static int len = 0;
    
    //欧拉筛
    public static void getPrimes(int n) {
    
    
        //遍历所有情况
        for (int i = 2; i <= n; i++) {
    
    
            if (!flag[i]) primes[cnt++] = i;
            //枚举所有primes数组中的情况来提前构造合数
            for (int j = 0; j < cnt && primes[j] * i <= n; j ++) {
    
    
                int pre = primes[j] * i;
                flag[pre] = true;
                if (i % primes[j] == 0) break;
            }
        }
    }
    
    //dfs进行暴搜
    //last:表示上一个用的质数的下标(primes数组的下标)是什么
    //pro:当前计算res答案是多少
    //s: 表示每次处理一个()后还有剩余的值
    public static void dfs(int last, int pro, int s) {
    
    
        //表示当前已经把s凑出来了,记录答案
        if (s == 1) {
    
    
            res[len++] = pro;
            return;
        }
        //剪枝:提前判断当前(s-1)是否是一个质数,若是(s-1)>上一个质数 && (s-1)是一个质数
        //直接来进行计算res结果值
        int pre = last >= 0 ? primes[last] : 1;
        if (s - 1 > pre && isPrime(s - 1)) {
    
    
            res[len++] = pro * (s - 1);
        }
        //枚举所有以i作为下标的质数,实际就是N公式中的pi
        for (int i = last + 1; primes[i] <= s / primes[i]; i++) {
    
    
            int p = primes[i];
            //j指的是枚举()中的各种情况,例如i = 2,此时枚举情况为(1 + 2)、(1 + 2 + 2*2)、(1 + 2*2 + 2*2*2)
            for (int j = 1 + p; j <= s; p *= primes[i], j += p) {
    
    
                //当前能够整除情况则进入下一个层 
                if (s % j == 0) {
    
    
                    //下一层从primes下标为[i + 1]的开始(因为for循环是从last+1开始的),当前括号*之前的值 = pro * p,若是j = (1 + 2 + 2*2),此时
                    //p就是2*2=4,这个p实际上就是N公式里的一个2平方
                    //目标约数和为s,到了下一层其剩余和即为s / j
                    dfs (i, pro * p, s / j);
                }
            }
        }
    }
    
    //判断是否是质数(由于之前primes数组仅仅开了sqrt(20亿)也就只有50万,所以这里需要进行遍历一遍质数数组来进行判断校验)
    public static boolean isPrime(int x) {
    
    
        //若是x在50万范围,直接从flag数组中判断返回即可
        if (x < N) return !flag[x];
        //若是>=50万,那么就进行遍历质数数组看是否有能够整除的,如果有那么直接返回
        for (int i = 0; primes[i] <= x / primes[i]; i++) {
    
    
            if (x % primes[i] == 0) return false;
        }
        return true;
    }
    
    public static void main(String[] args) throws Exception{
    
    
        //欧拉筛
        getPrimes(N - 1);
        String line = cin.readLine();
        //读取数据
        while (line != null && line.length() > 0) {
    
    
            //目标约数之和为s
            int s = Integer.parseInt(line);
            dfs(-1, 1, s);
            out.println(len);
            if (len != 0) {
    
    
                //对结果进行排序
                Arrays.sort(res, 0, len);
                //输出
                for (int i = 0; i < len; i++) {
    
    
                    out.print(res[i] + " ");
                }
                out.println();
                len = 0;//重置结果数组的长度为0
            }
            out.flush();
            line = cin.readLine();
        }
    }
}

image-20230124131816932


Example 4: AcWing 1299. Wuzhishan (Extended Euclid)

analyze

The total length is n, the distance of each somersault is d, the initial position is x, and the target position is y. If the target point cannot be reached, Impossible is output, and if it can be reached, the minimum number of somersaults is output.

The formula corresponding to 2 in this question is: x + bd = y (mod n), which means starting from point x, adding the distance of b somersaults, and finally obtaining the position as the target point mod the entire circle distance.

Then convert: x + bd = y (mod n) = y + an, which means point y, plus the length of circle a.

x + bd = y + an converts the following formula, where x, d, y, n are known, converted to: -an + bd = y - x

image-20230124173448404

Note: At this time, this formula corresponds to the formula of Extended Euclidean, gcd(a, b) = d, and the corresponding equation ax + by = d holds true, which can be reversed by using Extended Euclidean Introduce a and b in the formula of this question.

At this time, the idea of ​​this question is: use the extended Euclidean algorithm to find gcd(n, d) = gcd, and then judge whether yx can be divisible to obtain the greatest common divisor. If it cannot be divisible, output Impossible, and if it can be divisible, proceed Calculate the fewest number of somersaults.

Why is it possible to determine whether there is no solution if the gcd obtained by yx is divisible ?

  • Because -an + bd obtains a greatest common divisor, if the obtained greatest common divisor cannot divide y - x, then there must be no solution, and then output impossible.

How to calculate the minimum number of somersaults ?

When we come and go for extended Euclidean calculations, the actually calculated formula is: -an + bd = gcd(n, d), and for the title it should be -an + bd = y - x, in order to make the right formula The child also has yx, at this time we need to multiply both sides by (y - x) / gcd(n, d), at this time b is our final b result value of -an + bd = y - x!

At this time, the value of b is only a set of ab solutions in -an + bd = y - x, and all ab solutions can be obtained through a set of solutions by using the extended Euclidean formula, then we only need to get the minimum A b solution of !

Corresponding to the current topic - an + bd = y - x, we want to get the minimum value of b, first get the following two formulas:

  • b = b0 - kn’
  • n’ = n / (y - x)

At this time, all the current solutions of b are b = b0 - k.(n / d), where n and d are constants. For the minimum value of b we need to use, we only need to use b0 % ( n / (y - x)) That is, this b0 can actually be obtained, but we cannot determine k, so we can directly use the obtained result value b to mod, that is, the final minimum value is b = b mod (n /(y - x)).

And because in order to avoid negative numbers in b mod (n / (y - x)), we set n = n / (y - x), and then (b mod n + n) % n can convert negative numbers into positive numbers!

Solution: Extended Euclid

Complexity analysis: time complexity O(logn); space complexity O(1)

import java.io.*;
import java.util.*;

class Main {
    
    
    
    static final Scanner cin = new Scanner(System.in);
    
    static int t;
    static long n, d, x, y;
    
    //扩展欧几里得
    public static long exGcd(long a, long b, long[] arr) {
    
    
        if (b == 0) {
    
    
            arr[0] = 1;
            arr[1] = 0;
            return a;
        }
        //递归求得最大公约数
        long gcd = exGcd(b, a % b, arr);
        //反向推导求得一组x,y解: x = y',y = x' - a/b*y'
        long temp = arr[0];
        arr[0] = arr[1];
        arr[1] = temp - a / b * arr[1];
        return gcd;
    }
    
    public static void main(String[] args) {
    
    
        int t = cin.nextInt();
        while (t != 0) {
    
    
            //读取数据
            n = cin.nextLong();
            d = cin.nextLong();
            x = cin.nextLong();
            y = cin.nextLong();
            long[] arr = new long[2];
            //获取到最大公约数和一组xy解
            long gcd = exGcd(n, d, arr);
            //得到一组x与y解(这里用a与b表示)
            long a = arr[0];
            long b = arr[1];
            //若是y - x能够整除gcd(n, d)那么此时就说明有解
            if ((y - x) % gcd != 0) {
    
    
                System.out.println("Impossible");
            }else {
    
    
                //ax + by = gcd(a, b)  转换为  ax + by = y - x,所以两边需要乘上(y - x) / gcd(a, b)
                b *= (y - x) / gcd;
                //接着需要进行计算最小值:b = b0 - kn’、n’  = n / (y - x)
                //由于上面式子转换仅仅只是b变量进行了转换,所以n依旧使用原先的gcd进行转换
                n /= gcd;
                //避免b % n为负数情况
                System.out.println((b % n + n) % n);
            }
            
            t--;
        }
    }
}

image-20230124202624548

exercise

Problem 1: AcWing 1223. Maximum Scale (Medium, Blue Bridge Cup)

analyze

Topic link: AcWing 1223. Maximum ratio

This question is to give you a geometric sequence, the ratio is constant, but the question will only give some of the geometric sequence, so that you can find the largest geometric ratio in the geometric sequence.

The amount of data is not large, and there are only 10 numbers in the geometric sequence, but the maximum value is relatively large and the long type needs to be used.

Suppose the complete original arithmetic sequence is: a, a * pq \frac{p}{q}qp, a * ( p q \frac{p}{q} qp)2, a * ( p q \frac{p}{q} qp)3, a * ( p q \frac{p}{q} qp)4, … , a * ( p q \frac{p}{q} qp)n-1

Let's extract a few of them: b1, b2, b3, b4, then the composition of each number is ( pq \frac{p}{q}qp) k , for each group of b[i] to the ratio of b[i - 1] we can find, namely: pkqk \frac{p^k}{q^k}qkpkp k and q k in , for these two upper and lower values, we can obtain them by finding the greatest common divisor.

At this time, we return to the topic and say that let us obtain ( pq \frac{p}{q}qp) The maximum value of k is actually to let us find the greatest common divisor of the exponent, and for the greatest common divisor of the exponent, we need to use the rolling and subtraction method. The knowledge points can be seen: rolling and rolling and rolling and subtracting

Derivation is gcd(x,y) = gcd(y,x%y) = gcd(y,x−y)carried out by rolling and subtracting: f(p x ,p y ) = p gcd(x,y) = p gcd(y,x−y) = f(p y ,p( x−y )) = f( p y , pxpy \frac{px}{py}pypx), that is, you can ask for

Find the greatest common divisor power p gcd(x,y) of the powers of p x and p y .

Why can't the exponent use the rolling and dividing method ? See the following example, the subtraction method in this question is to subtract the exponent

  • Use rolling and dividing: gcd(5 2 , 5 3 ) = 5 2
  • Use rolling subtraction: gcd_sub(5 2 , 5 3 ) = 5 1

Problem Solution: Rolling and turning subtraction method (more phase subtraction technique)

Complexity analysis: time complexity O(n.logn), sorting complexity; space complexity O(n)

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final int N = 110;
    static int n;
    static long[] x = new long[N], p = new long[N], q = new long[N];
    
    
    //最大公约数(辗转相除)
    public static long gcd(long a, long b) {
    
    
        return b == 0 ? a : gcd(b, a % b);
    }
    
    //辗转相减,求得指数的最小公约数
    public static long gcd_sub(long a, long b) {
    
    
        if (a < b) {
    
    
            long temp = b;
            b = a;
            a = temp;
        }
        if (b == 1) return a;
        return gcd_sub(b, a / b);
    }
    
    public static void main(String[] args) throws Exception{
    
    
        n = Integer.parseInt(cin.readLine());
        String[] ss = cin.readLine().split(" ");
        for (int i = 0; i < n; i ++ ) {
    
    
            x[i] = Long.parseLong(ss[i]);
            //System.out.println(x[i]);
        }
        //对所有数字进行排序
        Arrays.sort(x, 0, n);
        
        
        //记录p与q数组成对的数量
        int cnt = 0;
        
        //查询所有数字乘数p/q的p与q的值
        for (int i = 1; i < n; i ++ ) {
    
    
            if (x[i] != x[i - 1]) {
    
    
                //获取到两个数的最大公约数
                long gcd = gcd(x[i], x[0]);
                //利用最大公约数来计算得到分数中的p与q
                p[cnt] = x[i] / gcd;
                q[cnt++] = x[0] / gcd;
            }
        }
        
        //开始计算所有(p/q)^n最大公约数
        long P = p[0];
        long Q = q[0];
        for (int i = 1; i < cnt; i ++ ) {
    
    
            P = gcd_sub(P, p[i]);
            Q = gcd_sub(Q, q[i]);
        }
        System.out.println(P + "/" + Q);
    }
}

image-20230126161706453


Exercise 2: Acwing 1301. C loops (simple, extended Euclidean)

Topic link: Acwing 1301. C loop

analyze

The k-bit system means that all variables can only store k bits. Then the value of +c each time is actually the value of mod 2 k .

At this point we can list the equation: (A + xC) mod 2 k = B, where A, C, and B are fixed values, and mod 2 k can actually be replaced by y.2 k .

At this point (A + xC) mod 2 k = B converts to A + xC - y.2 k = B converts to xC - y.2 k = B - A

image-20230126163228402Those circled in red are constants.

At this point, we can think of extending Euclidean: xa + yb = d, which can be reversed to obtain a set of solutions for x and y, and all solutions can be obtained from a set of solutions.

The corresponding formula is as follows:

image-20230126163710124

In fact, in the end we get the formula for this question as follows:

x' = x / (B - A)
y' = y / (B - A)

x = x0 + ky'
y = y0 + kx'

For judging whether it is possible to cycle all times, we only need to judge whether B - A can mod gcd(a, b). In the end, what we require is the number of times of the cycle, which is x = x0 % y'.

Solution: Extended Euclid

Complexity analysis: time complexity O(logn); space complexity O(1)

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final Scanner cin = new Scanner(System.in);
    static long A, B, C, K;
    
    //扩展欧几里得
    public static long exGcd(long a, long b, long[] arr) {
    
    
        if (b == 0) {
    
    
            arr[0] = 1;
            arr[1] = 0;
            return a;
        }
        long d = exGcd(b, a % b, arr);
        //通过公式去化解转为 x = y',y = x‘ - a/b*y'
        long temp = arr[0];
        arr[0] = arr[1];
        arr[1] = temp - a / b * arr[1];
        return d;
    }
    
    public static void main(String[] args) throws Exception{
    
    
        while(!isStop()) {
    
    
            //若是A==B,则输出0
            if (A == B) {
    
    
                System.out.println(0);
                continue;
            }
            // x.C - y.2^k = B - A
            //计算C与2^k的最大公约数
            long[] arr = new long[2];
            //提前定义好 x.C + y.2^k = gcd(C, 2^k)  =>  用a替代为C,b替代为2^k
            long a = C;
            long b = 1L << K;//注意,这个b变量必须使用1L,表示long类型,否则有误
            long gcd = exGcd(a, b, arr);
            if ((B - A) % gcd != 0) {
    
    
                System.out.println("FOREVER");
            }else {
    
    
                long x = arr[0];
                long y = arr[1];
                //将 x.a + y.b = gcd(a, b) 转为  x.a - y.b = B - A
                //此时只需要将这个x去进行一个转换
                x *= (B - A) / gcd;
                
                //若是想要取得一个最小运行次数x
                //y' = y / gcd
                b = b / gcd;
                
                //取得最小整数  x = x0 % b
                System.out.println((x % b + b) % b);
            }
        }
    }
    
    public static boolean isStop() throws Exception{
    
    
        A = cin.nextLong();
        B = cin.nextLong();
        C = cin.nextLong();
        K = cin.nextLong();
        return A == 0 && B == 0 && C == 0 && K == 0;
    }
}

image-20230126172306946


2. DFS

exercise

Exercise 1: AcWing 1225. Regular problems (medium, dfs and stack)

analyze

First of all, to understand the meaning of the question, there are a total of four symbols |, (), and x. Here are some examples:

xx|xxx    =>  xxx     //|选择左右两边最多的一组
(xx|xxx)x   => xxxx   //()与左右两边进行相连接

The given range of the topic is 100, and it is guaranteed to be legal.

Stack simulation idea:

遇到(、x、|符号直接入栈
遇到)开始进行匹配规则:
	循环出栈直到出现(,过程中可能会有|来进行计数判断选择最大的个数,最终来进行出栈(

dfs idea: imagine the entire string as a tree, and perform dfs on the string from left to right.

  • ① If it is encountered (then recurse down one layer until it matches) end.
  • ②If you encounter |, perform dfs() recursion on the right side of |xxx, and take a maximum value between the obtained length and the current length.
  • ③If it is encountered), it will directly break to end.
  • ④ If you encounter x, perform res+1 at this time.

image-20230126195812088

Solution 1: Stack simulation

Complexity analysis: time complexity O(n); space complexity O(n)

import java.util.Scanner;
import java.util.Stack;

class Main {
    
    

    static final Scanner cin = new Scanner(System.in);
    static Stack<Character> s = new Stack<>();
    
    //假设碰到)时来进行的计数操作
    public static void count() {
    
    
        //碰到)
        int cnt = 0;
        int c = 0;
        while (!s.isEmpty() && s.peek() == 'x') {
    
    
            c++;
            s.pop();
            cnt = Math.max(cnt, c);
            //如果说碰到了|,重新计数
            if (!s.isEmpty() && s.peek() == '|') {
    
    
                c = 0;
                s.pop();
            }
        }
        if (!s.isEmpty() && s.peek() == '(') {
    
    
            //此时碰到(
            s.pop(); 
        }
        //入栈cnt个x
        for (int i = 1; i <= cnt; i++) {
    
    
            s.push('x');
        }
    }

    public static void main(String[] args) {
    
    
        String line = cin.next();
        for (char ch: line.toCharArray()) {
    
    
            if (ch == '(' || ch == '|' || ch == 'x') {
    
    
                s.push(ch);
            }else {
    
    
                count();
            }
        }
        //结束之后再计算下,可能会出现情况:xx|xxxxx
        count();
        System.out.println(s.size());

    }
}

image-20230126200119570

Solution 2: dfs

Complexity analysis: time complexity O(n); space complexity O(n)

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final Scanner cin = new Scanner(System.in);
    static char[] arr;
    static int k;
    
    //计数
    public static int dfs() {
    
    
        int res = 0;
        while (k < arr.length) {
    
    
            //匹配(.....)
            if (arr[k] == '(') {
    
    
                k++;
                res += dfs();
                k++;
            }else if (arr[k] == ')') {
    
     //(的结束递归
                break;
            }else if (arr[k] == '|') {
    
      //比较左右最大数量 ...|...
                k++;
                res = Math.max(res, dfs());
            }else {
    
    
                //若是碰到x
                k++;
                res++;
            }
        }
        return res;
    }
    
    public static void main(String[] args) {
    
    
        arr = cin.next().toCharArray();
        int res = dfs();
        System.out.println(res);
        cin.close();
    }
    
}

image-20230126200114538

Exercise 2: AcWing 1243. Candy (state compression + IDA* and dp state compression, blue bridge cup)

Topic link: AcWing 1243. Candy

analyze

Idea 1: State compression + IDA* (dfs)

For duplicate coverage problems consider using IDA*.

  • Repeated Coverage Problem: Given a matrix, select the least number of rows such that all columns are covered.

For IDA* there are three parts to consider:

  1. Iterative deepening: judge layer by layer to see if it can be completely covered.
  2. Select the fewest columns: Select as few cases as possible to search.
  3. Feasibility pruning: by using an evaluation function h(state) to indicate at least how many rows are needed for the state state. If it matches the current number of searched rows, continue down, if not, end pruning in advance.

The entire complete idea can be seen in the code comments, which are very detailed.

Here I will post some instructions for state compression to perform binary operations:

image-20230127134622111

Idea 2: state compression dp

State representation: f[i][j]Indicates the number of selected minimum candy packages for the previous i package of candies and the state is j.

Initialization: f[i][0]=0, others default to the maximum value.

State calculation:f[i][j] = min(f[i][j], f[i][j & ~c[i]] + 1)

  • j & ~c[i]Indicates that the binary value of the target state is |merged with c[i] into the binary value j of the current state.

Solution 1: IDA*(dfs)

Complexity analysis: Time complexity O(b^d), where b is the branching factor and d is the depth of the first solution. Space complexity O(d)

import java.util.*;
import java.io.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final int N = 110, K = 22;
    //n表示行数,m表示糖果种类数量,k表示每袋有几个糖果
    static int n, m, k;
    //candy表示每袋糖果的二进制表示
    //log2表示根据二进制位去映射对应的糖果品类,下标是二进制位,值就是糖果品类编号
    //若是key:0001也就是1,value就是糖果品类编号0
    //若是key: 0010也就是2,value就是糖果品类编号1
    //糖果品类有M种,默认在初始化时进行编号0 - M-1
    static int[] candy = new int[N], log2 = new int[1 << K];
    //存储key为糖果类型编号,value为糖果包装有该糖果编号的二进制值
    static Map<Integer, List<Integer>> map = new HashMap<>();
    
    
    public static void main(String[] args) throws Exception{
    
    
        String[] ss = cin.readLine().split(" ");
        n = Integer.parseInt(ss[0]);
        m = Integer.parseInt(ss[1]);
        k = Integer.parseInt(ss[2]);
        //初始化糖果品类编号对应二进制位的映射
        //log2[1] = 0,log2[2] = 1,log2[4] = 2 ... 
        for (int i = 0; i < m; i ++ ) {
    
    
            log2[1 << i] = i;
        }
        //读取每袋糖果
        for (int i = 0; i < n; i ++ ) {
    
    
            ss = cin.readLine().split(" ");
            //对每袋糖果中的多个品类来进行状态压缩到一个二进制curCandy
            int curCandy = 0;
            for (int j = 0; j < k; j ++ ) {
    
    
                int candyType = Integer.parseInt(ss[j]);//读取到糖果编号
                //curCandy更新当前的糖果袋子具有的糖果种类,例如二进制形式curCandy = 00000,candyType = 1,而之前在log2中说明糖果种类为[0,M-1]
                //所以(1 << (candyType - 1))即为00001,此时00000 | 00001 = 00001
                //同上其他情况,curCandy = 00001,candyType = 3,此时此时00001 | 00100 = 00101
                curCandy = curCandy | (1 << (candyType - 1));
            }
            
            candy[i] = curCandy;//将每袋糖果具有的糖果类别进行状态压缩后添加到candy数组中
            
            //记录指定糖果类型编号有哪些袋糖果
            for (int j = 0; j < m; j ++ ) {
    
    
                //判断当前糖果包中是否有对应编号为j的糖果
                if (((curCandy >> j) & 1) == 1) {
    
    
                    if (!map.containsKey(j)) {
    
    
                        map.put(j, new ArrayList<>());
                    }
                    List<Integer> packages = map.get(j);
                    packages.add(curCandy);
                }
            }
        }
        //若是在map中具有的糖果类型种类没有m个,那么直接结束
        if (map.size() < m) {
    
    
            System.out.println("-1");
        }else {
    
    
            //1、迭代加深
            //进行尝试递归寻找糖果包方案数量
            int count = 0;
            //数量上限为糖果的品类,若是超过上限还没有找到说明肯定没有该方案
            while (count <= m) {
    
    
                //判断当前选择count数量的糖果包是否能够集全
                if (dfs(count, 0)) {
    
    
                    break;
                }else {
    
    
                    count++;
                }
            }
            //此时得到方案数
            System.out.println(count);
        }
    }
    
    //尝试寻找方案数量
    //count:表示当前还能选择的糖果包数量
    //state:表示当前已选糖果类型的状态,若是M为5,达到11111即可表示已经选中
    public static boolean dfs(int count, int state) {
    
    
        //3、使用估价函数来判断当前状态是否能够继续往下进行
        //若是当前不能选糖果包了 或者 还可以选并且至少需要糖果包的数量>当前剩余的数量
        if (count == 0 || mustNeed(state) > count) {
    
    
            //若是m为5,则判断当前已经状态state是否为11111
            return state == (1 << m) - 1;
        }
        //2、选择尽可能少的列
        //寻找还没有凑齐的多个糖果类型(从右往左开始)中最少糖果包的那个糖果列
        int minCol = -1;
        for (int i = (1 << m) - 1 - state; i > 0; i -= lowbit(i)) {
    
    
            //获取到二进制位从右往左第一个1,也就是第一个还未选择的糖果类型
            int col = log2[lowbit(i)];
            if (minCol == -1 || map.get(minCol).size() > map.get(col).size()) {
    
    
                minCol = col;
            } 
        }
        //枚举最少数量的糖果类型列,进行递归处理
        for (int pack: map.get(minCol)) {
    
    
            //还能选择的糖果数量-1,当前已经选择糖果状态列补上当前糖果包有的糖果列
            //state为00101,pack为00010,此时state | pack即为00111
            if (dfs(count - 1, state | pack)) {
    
    
                return true;
            }
        }
        return false;
    }
    
    //当前状态最少需要的糖果包数
    //state:表示当前已选糖果类型的状态
    public static int mustNeed(int state) {
    
    
        int ans = 0;
        //(1 << m) - 1 - state:表示的是当前还未选的糖果类型二进制状态
        for (int i = (1 << m) - 1 - state; i > 0;) {
    
    
            //当前所需要的糖果类型行号
            int col = log2[lowbit(i)];
            //获取到对应糖果类型的所有糖果
            List<Integer> packages = map.get(col);
            //来将该行对应的所有糖果包都去进行消除当前i二进制状态中与糖果包共有的1
            for (int pack: packages) {
    
    
                //假设i二进制为:11111,pack为00101
                //那么i & ~pack = 11010,相当于消去该糖果包有的糖果类型
                //~pack实际上就是表示所有二进制为取反,原本pack=00100,~pack即可转为11011
                i = i & ~pack;
            }
            ans++;
        }
        return ans;
    }
    
    //从右往左得到第一个1的下标k(从0开始),返回的结果值为2^k
    //例如x的二进制位0010,此时下标k为1,返回值就是2^1 = 2
    public static int lowbit(int x) {
    
    
        return x & -x;
    }
    
}

image-20230127123030122

Solution 2: State compression dp

Complexity analysis: time complexity O(n.2 m ), and the maximum value of m is 20, which is 100*1.04 million, about 10 million calculations. Space complexity O(2 m )

import java.io.*;
import java.util.*;

class Main {
    
    
    
    static final BufferedReader cin = new BufferedReader(new InputStreamReader(System.in));
    static final int N = 110, K = 20, INF = 101;
    //c表示所有糖果包的状态压缩;
    static int[] c = new int[N];
    //f表示从前i个物品中选且状态是j的最小糖果包数量。
    static int[] f = new int[1 << K + 5];
    static int n, m, k;
    
    public static void main(String[] args) throws Exception{
    
    
        String[] ss = cin.readLine().split(" ");
        n = Integer.parseInt(ss[0]);
        m = Integer.parseInt(ss[1]);
        k = Integer.parseInt(ss[2]);
        //初始化每个糖果包的状态压缩
        for (int i = 1; i <= n; i ++ ) {
    
    
            ss = cin.readLine().split(" ");
            for (int j = 1; j <= k; j ++ ) {
    
    
                int candyType = Integer.parseInt(ss[j - 1]);
                c[i] |= 1 << (candyType - 1);
            }
        }
        //初始化状态数组
        for (int i = 1; i < 1 << m; i ++ ) f[i] = INF;
        //一种口味都没有情况最少是0包糖果
        f[0] = 0;
        //遍历所有的糖果包
        for (int i = 1; i <= n; i ++ ) {
    
    
            //遍历所有1 - 2^m-1状态(从大到小)
            for (int j = (1 << m) - 1; j >= 0; j -- ) {
    
    
                //j & ~c[i]表示当前二进制状态j去除掉c[i]状态的共有1
                f[j] = Math.min(f[j], f[j & ~c[i]] + 1);
            }
        }
        if (f[(1 << m) - 1] == INF) {
    
    
            System.out.println("-1");
        }else {
    
    
            System.out.println(f[(1 << m) - 1]);
        }
    }
}

image-20230127134838882


reference article

[1] Example 1 Arithmetic Sequence: AcWing 1246. Arithmetic Sequence-Problem Solution 1 , AcWing 1246. Arithmetic Sequence-Problem Solution 2 , AcWing 1246. Arithmetic Sequence (Lanqiao Cup C++ AB Group Tutorial Class)-y total video explanation

[2] Example 3 Smart Stefan: AcWing 1296. Smart Stefan—Detailed Solution , AcWing 1296. Smart Stefan (Lanqiao Cup C++ Group AB Tutorial Course)—Video , AcWing 1296. Smart Stefan (Java )

[3]. Example 4 Wuzhishan: AcWing 1299. Wuzhishan-Problem Solution , AcWing 1299. Wuzhishan (extending the relationship of Euclid X1, Y1, X2, Y2) , AcWing 1299. Number Theory - Extending Euclid

[4]. Exercise 1 Maximum ratio: AcWing 1223. Maximum ratio (Java version)

[5]. Regular problems: AcWing 1225. Java infix expression ideas + recursive two solutions , AcWing 1225. Regular problems (Lanqiao Cup C++ AB group tutorial) , AcWing 1225. Regular problems - stack practice

[6]. Candy: algorithm - Artificial Intelligence: Time Complexity of IDA* Search , AcWing 1243. Candy (IDA* / DP Detailed Notes) , AcWing 1243. Candy (Java Edition) , AcWing 1243. Candy–>dfs+ Pruning + Sorting Optimization + Deletion Optimization + IDA* + Shape Compression , AcWing 1243. Candy (Lanqiao Cup C++ AB group tutorial)

Guess you like

Origin blog.csdn.net/cl939974883/article/details/128770442