AcWing Blue Bridge Cup Group AB Tutorial 08, Zahlentheorie

Vorwort

Um einige Algorithmusfragen im Interview beantworten zu können, habe ich mich vor einiger Zeit auf den Weg gemacht, die Fragen zu bürsten. Die meisten davon werden auf der Likou-Plattform erstellt. Die aktuelle Punktzahl liegt bei 400+. Darüber hinaus nach Als ich an der neuen Schule ankam, erfuhr ich, dass die Schule auch den Blue Bridge Cup organisiert. Ähnliche Programmierwettbewerbe möchte ich noch einmal versuchen, ich möchte den Algorithmus systematisch lernen (vorher war es hauptsächlich das Haupt-Back-End-Projekt usw.). Ich wusste nach kurzer Zeit nicht viel über den Algorithmus und es war vor einiger Zeit das erste Mal, dass ich die Acwing-Plattform besuchte. Ich habe das Gefühl, dass die oben genannten Kurse relativ systematisch sind und es viele Fragen dazu gibt die Plattform, also habe ich vor, die Acwing-Kurse eine Zeit lang zu verfolgen und lasst uns zusammenarbeiten!

  • Derzeit habe ich vor, der Java-Gruppe beizutreten, daher sind alle Lösungen Java.

Verzeichnisindex aller Blog-Dateien: Index des Blog-Verzeichnisses (kontinuierlich aktualisiert)

Eine Liste der Gier-Übungen in diesem Kapitel: Links zu Java-Lösungen für alle Themen

Vorlesung 8 Studienzeitraum: 20.01.2023-27.01.2023

Bild-20230127135833702

Beispiel:

Übung:

1. Zahlentheorie

Beispiel

Beispiel 1: AcWing 1246. Arithmetische Folge (größter gemeinsamer Teiler, C++B-Frage 7 des 10. Lanqiao Cup Provincial Competition)

analysieren

Die Datenmenge beträgt 100.000 und die zeitliche Komplexität beträgt O(n.logn), O(n).

Zunächst ist die arithmetische Folge wie folgt:

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

Im Titel heißt es, dass nur ein kleiner Teil dieser Reihe arithmetischer Folgen angegeben ist und Sie aufgefordert werden, die kürzeste Anzahl arithmetischer Folgen zu finden. Zu diesem Zeitpunkt müssen wir die Zahlen der angegebenen Zahlenreihe verwenden Finden Sie den entsprechenden d-Wert. Wie finde ich den Wert von d?

Wir können beobachten, dass jede Zahl in der arithmetischen Folge aus einem festen Wert x und einem Vielfachen des entsprechenden d besteht. Dann können wir uns vorstellen, d mithilfe des größten gemeinsamen Teilers zu finden.

Beispiel: Wir verwenden x+3d - x = 3d, x + 3d - (x - d) = 2d, lassen 2d und 3d kommen und gehen, um den größten gemeinsamen Teiler zu berechnen, und dann kann d erhalten werden.

Wie erhalten wir ihre Zahlen für die gesamte arithmetische Folge?

Menge = (Max - Min) / d + 1

Für Sonderfälle, zum Beispiel: 1 1 1, das heißt, wenn d 0 ist, sollte seine Zahl n sein.

Dann können wir zu AC gehen!

Problemlösung: Größter gemeinsamer Teiler

Komplexitätsanalyse: Zeitkomplexität O(n.logn); Raumkomplexität 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);
    }
}

Bild-20230120113346901

Tatsächlich können wir optimieren, da der Zweck der Sortierung nur darin besteht, den Minimalwert und den Maximalwert zu finden. Wir müssen nur O (n) verwenden, um ihn zu finden:

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);
    }
}

Bild-20230120113519570


Beispiel 2: AcWing 1295. Faktorkette von X (Grundsatz der Arithmetik, Eulersches Sieb, Permutationen mehrerer Mengen)

Themenlink : 1295. Faktorkette von X

analysieren

Die N-Größe dieser Frage beträgt 1 Million, und die zeitliche Komplexität sollte in O(n.logn), O(n) kontrolliert werden.

Diese Frage wird anhand der Werte unserer beiden Lösungen analysiert.

Finden Sie die maximale Länge einer Sequenz :

Hier müssen wir den Grundsatz der Arithmetik lernen, der besagt: Jede natürliche Zahl N größer als 1, wenn N keine Primzahl ist, dann kann N eindeutig in das Produkt einer endlichen Anzahl von Primzahlen zerlegt werden. Alle ganzen Zahlen lassen sich eindeutig in die Form des Produkts mehrerer Primfaktoren zerlegen.

  • Die Produktformel lautet: N=P1 a1 P2 a2 P3 a3 * ... Pn an , wobei P1<P2<P3...<Pn alle Primzahlen sind und der Exponent ai eine positive ganze Zahl ist. Eine solche Zerlegung wird als Standardzerlegung von N bezeichnet. Der früheste Beweis wurde von Euklid gegeben.

Gemäß den Anforderungen an die Titelbeschreibung dieser Frage: Die Faktoren von X größer als 1 bilden eine streng steigende Folge, die jedes vorherige Element erfüllt, das das nächste Element teilen kann.

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

Die Beschreibung des Themas ist perfekt auf den Grundsatz der Arithmetik abgestimmt: N=P1 a1 P2 a2 P3 a3 * ... Pn an , wir lösen eine Zahl gemäß dem Grundsatz der Arithmetik auf und können dann die maximale Länge von erhalten die Folge als a1 + a2 + a3 + a4 + ... + an .

Warum? Zum Beispiel:

180 = 2 2 * 3 2 * 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

Gerade weil wir die Formel des Fundamentalsatzes der Arithmetik verwenden müssen, müssen wir die Siebmethode von Euler verwenden, um alle Primzahlen in 1 ~ n zu finden und den kleinsten Primfaktor jeder Zahl herauszufiltern . (Die Datenmenge in dieser Frage beträgt 1 Million und die Komplexität der Euler-Siebmethode ist O(n))

Für die Idee und den detaillierten Code der Euler-Screening-Methode (einschließlich naivem Screening, ägyptischem Screening und Euler-Screening): Euler-Screening-Methode der Zahlentheorie (einschließlich einfachem Screening, detaillierter Code für ägyptisches Screening)

Es ist sehr wichtig, den kleinsten Primfaktor jeder Zahl herauszufiltern, der zur Ableitung der entsprechenden Formel des Grundsatzes der Arithmetik verwendet werden kann. Beispielsweise kann aus 180 abgeleitet werden, dass der kleinste Primfaktor (Primzahl) 2 ist. und dann 180/4=45, der kleinste Primfaktor von 45. Der Faktor ist 3, 45/9=5, der kleinste Primfaktor von 5 ist 5 und die endgültige Zusammensetzung: 180 = 2 2 * 3 2 * 5 . An diesem Punkt können wir die entsprechenden a1, a2, a3 ... an erhalten und dann die maximale Länge der Sequenz ermitteln.

Die Anzahl der Sequenzen, die die maximale Länge erfüllen : Tatsächlich ist die Anzahl der Sequenzen mit der maximalen Länge vollständig angeordnet und dedupliziert

Der allgemeine Beweissatz von Y: Führen Sie zuerst eine Zuordnung durch, nicht die Zahl selbst, sondern das Inkrement der Zahl. Die ursprüngliche Folge ist a1, a2, a3 ... an, die abgebildete Folge ist a1, a 2 a 1 a2\ über a1eine 1eine 2, a 3 a 2 a3\über a2eine 2eine 3anan − 1 an\über an-1an 1ein, diese beiden Folgen entsprechen sich, wir können die zweite Folge finden, indem wir uns die erste Folge geben, und jede Zahl in der zweiten Folge ist auch ein Primfaktor, sodass die Anzahl der Folgen die vollständige Anordnung aller Primfaktoren der Zahl ist (muss doppelte Nummern entfernen).

Können wir auch mehrere Schemata für Sequenzen maximaler Länge erstellen? Lassen Sie uns den obigen Beweissatz anhand eines praktischen Beispiels veranschaulichen: 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

Tatsächlich suchen wir also nach der Nummer der Sequenz mit der maximalen Länge, um die Spalten anzuordnen (wobei die vollständige Anordnung derselben Elemente entfernt wird).

Die entsprechenden Multiset-Permutationen lauten wie folgt:

N Elemente sind vollständig angeordnet: N!

Entfernen Sie dieselben Elemente wie im folgenden Beispiel:

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

Die endgültige vollständige Permutation mit entfernten identischen Elementen ist: ( a 1 + a 2 + a 3 + . . . + an ) ! a 1 ! a 2 ! . . . an ! (a1 + a2 + a3 + ... + an )!\over a1!a2!...an!eine 1 ! eine 2 ! ... ein !( a 1 + a 2 + a 3 + ... + an ) !

Lösung: Zahlentheorie – Grundsatz der Arithmetik, Euler-Sieb, Permutationen mehrerer Mengen

Komplexitätsanalyse: Zeitkomplexität O(n); Raumkomplexität 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));
        }
        
    }
}

Bild-20230120180033030


Beispiel 3: AcWing 1296. Smart Stefanie

Essenz: heftige Suche + Beschneiden

Studienartikel: AcWing 1296. Smart Stefanie – Detaillierte Lösung , AcWing 1296. Smart Stefanie (Lanqiao Cup C++ AB Gruppen-Tutorial) – Video

analysieren

Lassen Sie uns zunächst die Bedeutung der Frage verstehen : Sie soll Ihnen die Summe der Teiler einer Zahl geben und Ihnen ermöglichen, mehrere Zahlen zu finden, die diese Bedingung erfüllen (es kann mehr als eine geben).

  • Beispiel: Die Summe der Teiler beträgt 42 und die Summe der Einschränkungen der Zahlen 20, 26 und 41 beträgt 42.
  • Die Teiler der Zahl 20 sind: 1 2 4 5 10 20, deren Summe 42 ist.

Die Idee der gewaltsamen Aufzählung in dieser Frage ist : Zählen Sie die Teiler aller Zahlen von 1 bis S-1 für eine Zahl S auf und beurteilen Sie dann, ob die Summe ihrer Teiler gleich S ist. Da der Maximalwert von S jedoch 2 Milliarden beträgt, kommt es definitiv zu einer Zeitüberschreitung.

An dieser Stelle kann die Formel für die Summe der Teiler verwendet werden : S = (1+p1+p1 2 +…+p1 a1 )(1+p2+p2 2 +…+p2 a2 )…(1+pn+pn 2 +… +pn an )

Wenn es einen Teiler s gibt, der die obige Formel erfüllt, dann ist 3x5x9x17x33x65x129 = 635037975, was relativ nahe an der Komplexität von O(n) liegt, dh der Maximalwert von S für diese Frage beträgt 2 Milliarden.

Für diesen Prozess wird dfs zum Suchen verwendet und zwei Ebenen von for-Schleifen werden zum Aufzählen verwendet:

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

Im Folgenden sind die drei Ergebniswerte 20, 26 und 41 angegeben, deren Summe der Teiler 42 beträgt. Sie können die Ergebniswerte sehen, die Sie mithilfe der Formel für die Summe der Teiler erhalten:

①20:1 2 4 5 10 20

  • 20 = 2 2 * 5 = (2 0 + 2 1 + 2 2 )*(5 0 +5 1 )=42

②26:1 2 13 26

  • 26 = 2 * 13 = (2 0 + 2 1 ) * (13 0 + 13 1 ) = 42

③41:1 41

  • 41 = 41 = (41 0 + 41 1 ) = 42

Wie kann man durch Beschneiden einige Berechnungen reduzieren?

  • Wir können feststellen, dass die tatsächliche Formel tatsächlich aus () * () besteht, sodass unser tatsächlicher Grenzbereich sqrt(s) sein kann. In diesem Fall müssen wir den Sonderfall S = (1 + pi) berücksichtigen.
  • Beispiel: Wenn die Summe der Teiler 42 beträgt, ist die größte zu diesem Zeitpunkt durchquerte Primzahl 7 und eine Ergebniszahl ist 41, da die Grenze sqrt(s) ist. In diesem Fall ist die Grenze nicht erreichbar Sie müssen nur berücksichtigen, ob Pi in S = (1 + Pi) eine Primzahl ist.

Darüber hinaus muss diese Frage Primzahlen screenen, sodass das Euler-Screening verwendet werden muss, um die O(n)-Komplexität zu erreichen. Informationen zu einfachen Screening-, Ella-Screening- und Euler-Screening-Ideen und Codes finden Sie im Blog: Euler-Screening-Methode von Zahlentheorie (einschließlich einfacher Rasterung , Angström-Filtercode für Details) .

Problemlösung: Euler-Sieb + Summe der Teiler (dfs) + Beschneiden

DFS-Code ohne sqrt (): Es müssen 2 Milliarden Array-Speicherplätze geöffnet werden, und diese Frage kann nicht ac sein

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);
            }
        }
    }
}

Beschneidungsoptimierung hinzufügen: Quadrat(e) durchqueren, Beurteilungsbedingung der S-1-Situation hinzufügen

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);
            }
        }
    }
}

Hinweis: Wenn Sie Java verwenden und System.out.print direkt verwenden, tritt eine Zeitüberschreitung auf. Es wird empfohlen, BufferedReader und PrintWriter zu verwenden

Vollständiger 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();
        }
    }
}

Bild-20230124131816932


Beispiel 4: AcWing 1299. Wuzishan (Erweiterter Euklid)

analysieren

Die Gesamtlänge beträgt n, die Distanz jedes Saltos beträgt d, die Anfangsposition ist x und die Zielposition ist y. Wenn der Zielpunkt nicht erreicht werden kann, wird Unmöglich ausgegeben, und wenn er erreicht werden kann, wird die Mindestanzahl ausgegeben Saltos werden ausgegeben.

Die Formel, die 2 in dieser Frage entspricht, lautet: x + bd = y (mod n), was bedeutet, vom Punkt x aus die Entfernung von b Saltos zu addieren und schließlich die Position als Zielpunkt mod für die gesamte Kreisentfernung zu erhalten.

Dann konvertieren Sie: x + bd = y (mod n) = y + an, was Punkt y plus die Länge des Kreises a bedeutet.

x + bd = y + an wandelt die folgende Formel um, wobei x, d, y, n bekannt sind, umgewandelt in: -an + bd = y – x

Bild-20230124173448404

Hinweis: Zu diesem Zeitpunkt entspricht diese Formel der Formel des erweiterten Euklidischen, gcd(a, b) = d, und die entsprechende Gleichung ax + by = d gilt, was durch Verwendung des erweiterten Euklidischen umgekehrt werden kann. Führen Sie a und b ein die Formel dieser Frage.

Zu diesem Zeitpunkt besteht die Idee dieser Frage darin: Verwenden Sie den erweiterten euklidischen Algorithmus, um gcd (n, d) = gcd zu finden, und beurteilen Sie dann, ob yx teilbar ist, um den größten gemeinsamen Teiler zu erhalten. Wenn es nicht teilbar ist, Ausgabe Unmöglich, und wenn sie teilbar ist, fahren Sie mit der Berechnung der geringsten Anzahl von Saltos fort.

Warum kann festgestellt werden, ob es keine Lösung gibt, wenn der durch yx erhaltene gcd teilbar ist ?

  • Da -an + bd einen größten gemeinsamen Teiler erhält, darf es keine Lösung geben, wenn der erhaltene größte gemeinsame Teiler y - x nicht teilen kann, und die Ausgabe ist dann unmöglich.

Wie berechnet man die Mindestanzahl an Saltos ?

Wenn wir erweiterte euklidische Berechnungen durchführen, lautet die tatsächlich berechnete Formel: -an + bd = gcd(n, d), und für den Titel sollte sie -an + bd = y - x lauten, um das Richtige zu tun Seite Das Kind hat auch yx, zu diesem Zeitpunkt müssen wir beide Seiten mit (y - x) / gcd(n, d) multiplizieren, zu diesem Zeitpunkt ist b unser endgültiger b-Ergebniswert von -an + bd = y - x!

Zu diesem Zeitpunkt ist der Wert von b nur ein Satz von ab-Lösungen in -an + bd = y - x, und alle ab-Lösungen können durch einen Satz von Lösungen unter Verwendung der erweiterten euklidischen Formel erhalten werden, dann müssen wir nur noch erhalten die minimale Ab-Lösung von !

Entsprechend dem aktuellen Thema - an + bd = y - x - möchten wir den Mindestwert von b ermitteln und erhalten zunächst die folgenden zwei Formeln:

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

Zu diesem Zeitpunkt sind alle aktuellen Lösungen von b b = b0 - k.(n / d), wobei n und d Konstanten sind. Für den Mindestwert von b, den wir verwenden müssen, müssen wir nur b0 % ( n) verwenden / (y - x)) Das heißt, dieses b0 kann tatsächlich erhalten werden, aber wir können k nicht bestimmen, sodass wir den erhaltenen Ergebniswert b direkt für mod verwenden können, dh der endgültige Mindestwert ist b = b mod (n /(y - x)).

Und weil wir, um negative Zahlen in b mod (n / (y – x)) zu vermeiden, n = n / (y – x) setzen und dann (b mod n + n) % n negative Zahlen in positive umwandeln können Zahlen!

Lösung: Erweiterter Euklid

Komplexitätsanalyse: Zeitkomplexität O(logn); Raumkomplexität 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--;
        }
    }
}

Bild-20230124202624548

Übung

Problem 1: AcWing 1223. Maximaler Maßstab (Mittel, Blue Bridge Cup)

analysieren

Themenlink: AcWing 1223. Maximales Verhältnis

Diese Frage soll Ihnen eine geometrische Folge geben, das Verhältnis ist konstant, aber die Frage gibt nur einen Teil der geometrischen Folge an, sodass Sie das größte geometrische Verhältnis in der geometrischen Folge finden können.

Die Datenmenge ist nicht groß und die geometrische Folge enthält nur 10 Zahlen, aber der Maximalwert ist relativ groß und der lange Typ ist erforderlich.

Angenommen, die vollständige ursprüngliche arithmetische Folge lautet: a, a * pq \frac{p}{q}Qp, a * ( pq \frac{p}{q}Qp) 2, a * ( pq \frac{p}{q}Qp) 3, a * ( pq \frac{p}{q}Qp) 4 , … , a * ( pq \frac{p}{q}Qp) n-1

Extrahieren wir einige davon: b1, b2, b3, b4, dann ist die Zusammensetzung jeder Zahl ( pq \frac{p}{q}Qp) k , für jede Gruppe von b[i] können wir das Verhältnis von b[i - 1] finden, nämlich: pkqk \frac{p^k}{q^k}QkPkp k und q k in , für diese beiden oberen und unteren Werte können wir sie erhalten, indem wir den größten gemeinsamen Teiler ermitteln.

An diesem Punkt kehren wir zum Thema zurück und sagen, dass wir ( pq \frac{p}{q}Qp) Der Maximalwert von k dient eigentlich dazu, den größten gemeinsamen Teiler des Exponenten zu finden, und für den größten gemeinsamen Teiler des Exponenten müssen wir die Roll- und Subtraktionsmethode verwenden. Die Wissenspunkte sind zu sehen: Rollen und Rollen und Rollen und Subtrahieren

Die Ableitung erfolgt gcd(x,y) = gcd(y,x%y) = gcd(y,x−y)durch Rollen und Subtrahieren: 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}p yp x), das heißt, Sie können danach fragen

Finden Sie die größte gemeinsame Teilerpotenz p gcd(x,y) der Potenzen von p x und p y .

Warum kann der Exponent nicht die Roll- und Divisionsmethode verwenden ? Siehe das folgende Beispiel. Die Subtraktionsmethode in dieser Frage besteht darin, den Exponenten zu subtrahieren

  • Verwenden Sie Rollen und Dividieren: gcd(5 2 , 5 3 ) = 5 2
  • Verwenden Sie eine rollierende Subtraktion: gcd_sub(5 2 , 5 3 ) = 5 1

Problemlösung: Roll- und Drehsubtraktionsmethode (mehr Phasensubtraktionstechnik)

Komplexitätsanalyse: Zeitkomplexität O(n.logn), Sortierkomplexität; Raumkomplexität 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);
    }
}

Bild-20230126161706453


Übung 2: Acwing 1301. C-Schleifen (einfache, erweiterte Euklidische)

Themenlink: Acwing 1301. C-Schleife

analysieren

Das K-Bit-System bedeutet, dass alle Variablen nur k Bits speichern können. Dann ist der Wert von +c jedes Mal tatsächlich der Wert von mod 2 k .

An dieser Stelle können wir die Gleichung auflisten: (A + xC) mod 2 k = B, wobei A, C und B feste Werte sind und mod 2 k tatsächlich durch y.2 k ersetzt werden kann .

An diesem Punkt wird (A + xC) mod 2 k = B in A + xC – y.2 k = B umgewandelt in xC – y.2 k = B – A

Bild-20230126163228402Die rot eingekreisten Werte sind Konstanten.

An diesem Punkt können wir über die Erweiterung von Euklidisch nachdenken: xa + yb = d, die umgekehrt werden kann, um eine Reihe von Lösungen für x und y zu erhalten, und alle Lösungen können aus einer Reihe von Lösungen erhalten werden.

Die entsprechende Formel lautet wie folgt:

Bild-20230126163710124

Tatsächlich erhalten wir am Ende die Formel für diese Frage wie folgt:

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

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

Um zu beurteilen, ob es möglich ist, alle Zeiten zu durchlaufen, müssen wir nur beurteilen, ob B - A gcd(a, b) modifizieren kann. Am Ende benötigen wir die Anzahl der Zyklen, also x = x0 % y'.

Lösung: Erweiterter Euklid

Komplexitätsanalyse: Zeitkomplexität O(logn); Raumkomplexität 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;
    }
}

Bild-20230126172306946


2. DFS

Übung

Übung 1: AcWing 1225. Regelmäßige Probleme (Medium, DFS und Stack)

analysieren

Um die Bedeutung der Frage zu verstehen, gibt es zunächst insgesamt vier Symbole |, () und x. Hier einige Beispiele:

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

Der angegebene Bereich des Themas beträgt 100 und es ist garantiert legal.

Ideen für die Stapelsimulation:

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

DFS-Idee: Stellen Sie sich die gesamte Zeichenfolge als Baum vor und führen Sie DFS von links nach rechts für die Zeichenfolge aus.

  • ① Wenn es gefunden wird (rekursieren Sie dann eine Ebene nach unten, bis es übereinstimmt), beenden Sie es.
  • ②Wenn Sie auf | stoßen, führen Sie eine dfs()-Rekursion auf der rechten Seite von |xxx durch und nehmen Sie einen Maximalwert zwischen der erhaltenen Länge und der aktuellen Länge.
  • ③Wenn es angetroffen wird, wird es direkt unterbrochen und beendet.
  • ④ Wenn Sie auf x stoßen, führen Sie zu diesem Zeitpunkt res+1 aus.

Bild-20230126195812088

Lösung 1: Stapelsimulation

Komplexitätsanalyse: Zeitkomplexität O(n); Raumkomplexität 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());

    }
}

Bild-20230126200119570

Lösung 2: dfs

Komplexitätsanalyse: Zeitkomplexität O(n); Raumkomplexität 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();
    }
    
}

Bild-20230126200114538

Übung 2: AcWing 1243. Candy (Zustandskomprimierung + IDA* und dp-Zustandskomprimierung, blauer Brückenbecher)

Themenlink: AcWing 1243. Candy

analysieren

Idee 1: Zustandskomprimierung + IDA* (dfs)

Bei Problemen mit der doppelten Abdeckung sollten Sie die Verwendung von IDA* in Betracht ziehen.

  • Problem der wiederholten Abdeckung: Wählen Sie bei einer gegebenen Matrix die geringste Anzahl von Zeilen aus, sodass alle Spalten abgedeckt sind.

Für IDA* sind drei Teile zu berücksichtigen:

  1. Iterative Vertiefung: Schicht für Schicht beurteilen, um zu sehen, ob sie vollständig abgedeckt werden kann.
  2. Wählen Sie die wenigsten Spalten aus: Wählen Sie so wenige Fälle wie möglich für die Suche aus.
  3. Machbarkeitsbereinigung: Mithilfe einer Bewertungsfunktion h(state) wird angegeben, wie viele Zeilen für den Statusstatus benötigt werden. Wenn es mit der aktuellen Anzahl der durchsuchten Zeilen übereinstimmt, fahren Sie fort, wenn nicht, beenden Sie die Bereinigung im Voraus.

Die gesamte Idee ist in den Codekommentaren zu sehen, die sehr detailliert sind.

Hier werde ich einige Anweisungen zur Zustandskomprimierung veröffentlichen, um binäre Operationen auszuführen:

Bild-20230127134622111

Idee 2: Zustandskomprimierung dp

Statusdarstellung: f[i][j]Gibt die Anzahl der ausgewählten Mindestbonbonpakete für das vorherige i-Paket an Bonbons an und der Status ist j.

Initialisierung: f[i][0]=0, andere verwenden standardmäßig den Maximalwert.

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

  • j & ~c[i]Zeigt an, dass der Binärwert des Zielzustands mit c[i] zum Binärwert j des aktuellen Zustands zusammengeführt wird.

Lösung 1: IDA*(dfs)

Komplexitätsanalyse: Zeitkomplexität O(b^d), wobei b der Verzweigungsfaktor und d die Tiefe der ersten Lösung ist. Raumkomplexität 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;
    }
    
}

Bild-20230127123030122

Lösung 2: Zustandskomprimierung dp

Komplexitätsanalyse: Zeitkomplexität O(n.2 m ), und der Maximalwert von m beträgt 20, was 100 * 1,04 Millionen entspricht, etwa 10 Millionen Berechnungen. Raumkomplexität 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]);
        }
    }
}

Bild-20230127134838882


Referenzartikel

[1] Beispiel 1 Arithmetische Folge: AcWing 1246. Arithmetische Folge – Problemlösung 1 , AcWing 1246. Arithmetische Folge – Problemlösung 2 , AcWing 1246. Arithmetische Folge (Lanqiao Cup C++ AB Group Tutorial Class) – vollständige Videoerklärung

[2] Beispiel 3 Smart Stefan: AcWing 1296. Smart Stefan – Detaillierte Lösung , AcWing 1296. Smart Stefan (Lanqiao Cup C++ Group AB Tutorial Course) – Video , AcWing 1296. Smart Stefan (Java)

[3]. Beispiel 4 Wuzishan: AcWing 1299. Wuzishan-Problemlösung , AcWing 1299. Wuzishan (Erweiterung der Beziehung von Euklid X1, Y1, X2, Y2) , AcWing 1299. Zahlentheorie - Erweiterung von Euklid

[4] Übung 1 Maximales Verhältnis: AcWing 1223. Maximales Verhältnis (Java-Version)

[5]. Regelmäßige Probleme: AcWing 1225. Ideen für Java-Infixausdrücke + rekursive zwei Lösungen , AcWing 1225. Regelmäßige Probleme (Lanqiao Cup C++ AB-Gruppen-Tutorial) , AcWing 1225. Regelmäßige Probleme – Stapelübungen

[6]. Candy: Algorithmus – Künstliche Intelligenz: Zeitkomplexität der IDA*-Suche , AcWing 1243. Candy (IDA* / DP Detaillierte Anmerkungen) , AcWing 1243. Candy (Java Edition) , AcWing 1243. Candy–>dfs+ Pruning + Sorting Optimierung + Löschoptimierung + IDA* + Formkomprimierung , AcWing 1243. Candy (Lanqiao Cup C++ AB Gruppen-Tutorial)

Supongo que te gusta

Origin blog.csdn.net/cl939974883/article/details/128770442
Recomendado
Clasificación