Kursdesign für die Algorithmusanalyse (4) Verwenden Sie die Divide and Conquer-Methode, um das Punktpaar und den Pfad mit einem Abstand zwischen zwei beliebigen Punkten im Baum von weniger als K zu ermitteln

Haftungsausschluss

Dieser Artikel ist nur eine persönliche Studiennotiz. Bitte beziehen Sie sich vorsichtig darauf. Wenn ein Fehler vorliegt, kritisieren und korrigieren Sie ihn bitte.

Referenzartikel

Der erste Artikel befasst sich hauptsächlich mit dem Schwerpunkt des Baumes

Der zweite Artikel ist genau der gleiche wie diese Frage

https://blog.csdn.net/a_forever_dream/article/details/81778649

https://blog.csdn.net/jackypigpig/article/details/69808594

Anspruch:

(1) Verwenden Sie Pseudocode, um den Algorithmus zum Ermitteln des Schwerpunkts des Baums zu beschreiben.

(2) Wenn der folgende Baum als Eingabe verwendet wird, schreiben Sie den Lösungsprozess und das Lösungsergebnis zur Lösung des obigen Problems. Es ist erforderlich, den Änderungsprozess der Hauptvariablen in den Lösungsprozess zu schreiben.

(3) Schreiben Sie ein Programm, um das Problem zu lösen, und analysieren Sie die zeitliche Komplexität des Algorithmus.

Analyse

der gesamte Prozess:

1. Finden Sie den Schwerpunkt des Baumes.

2. Berechnen Sie das Abstandsarray von jedem Punkt zum Schwerpunkt.

3. Alle Punktpaare, die durch den Schwerpunkt verlaufen, abzüglich aller Punktpaare, die durch die Schwerpunktunterknoten verlaufen, um den zulässigen Punktlogarithmus zu erhalten

4. Wiederholen Sie die Vorgänge 1, 2 und 3 für die untergeordneten Knoten des Schwerpunkts (Rekursion).

1. Finden Sie den Schwerpunkt des Baumes.

Der Schwerpunkt des Baumes wird auch als Massenschwerpunkt des Baumes bezeichnet. Das heißt, für einen Knoten des Baums ist die maximale Anzahl von Knoten aller Teilbäume nach dem Löschen (im Vergleich zum Löschen anderer Knoten) die kleinste. wie das Bild zeigt:

Um den Schwerpunkt des Baums zu erhalten, besteht die erste Idee möglicherweise darin, alle Knoten einmal zu durchlaufen, jeden Knoten als Schwerpunkt zu behandeln, die Anzahl der Knoten in jedem Teilbaum zu berechnen und dann zu vergleichen. Dieses Gewaltgesetz ist jedoch unerwünscht. Es wird den gleichen Pfad viele Male berechnen, und wir können die maximale Anzahl von Teilbaumknoten mit jedem Punkt als Schwerpunkt erhalten, indem wir den gesamten Baum nur einmal scannen. Hier wird die Punktteilungs- und Eroberungsmethode des Baums verwendet. Teilen und Erobern, ich verstehe es als rekursiven Aufruf für die Durchquerung nach der Bestellung, aber Rekursion kann nur von einem Knoten als Ausgangspunkt rekursiert werden. Wie können wir die maximale Anzahl von Teilbaumknoten ab allen Punkten berechnen? Hier nehmen wir noch den obigen Baum als Beispiel. Verwenden Sie n, um die Anzahl der Knoten im gesamten Baum darzustellen, wobei n = 5 ist. Verwenden Sie Größe [i], um die Anzahl der Knoten im Baum darzustellen, die auf i verwurzelt sind. Verwenden Sie max_child [i], um die maximale Anzahl von Teilbaumknoten darzustellen, die auf i verwurzelt sind. Verwenden Sie min Um den Mindestwert in max_child [i] zu aktualisieren, wird der Schwerpunkt aktualisiert, und der Startwert von min ist eine große Zahl.

Entsprechend der Rekursion der nachfolgenden Durchquerung können wir zuerst die Größe [4] = 1 von Punkt 4 berechnen, und der rote Teil ist n-Größe [4] = 4 Knoten. Der Grund für diese Unterteilung ist, dass Punkt 4 nur einen übergeordneten Knoten hat, sodass der rote Teil immer als untergeordneter Baum davon verwendet werden kann. Beim Vergleich der Größe von Größe [4] und n-Größe [4] beträgt die maximale Anzahl von Teilbaumknoten, die an Punkt 4 verwurzelt sind, max_child [4] = 4, und min wird auf 4 von max_child [4] aktualisiert.

Berechnen Sie dann die Größe [5] = 1 von Punkt 5, die Punkt 4 ähnlich ist. Der rote Teil ist n-Größe [5] = 4 Knoten, die maximale Anzahl von Teilbaumknoten mit Punkt 5 als Wurzel max_child [5] = 4, min ist unverändert.

Berechnen Sie als nächstes Punkt 2, der sein eigener Knoten ist, plus die Größe seiner Teilbaumpunkte 4 und 5. Der grüne Teil ist sein Teilbaum, der Größe [4] = 1 Knoten, Größe [5] = 1 Knoten und der rote Teil ist n-Größe [2] = 2 Knoten, also Punkt 2 Die maximale Anzahl von Teilbaumknoten des Stamms max_child [2] = 2 und min wird auf 2 aktualisiert.

Als nächstes wird Punkt 3 berechnet, der 1 Knoten von sich selbst ist. Der rote Teil hat eine Größe von n [3] = 4 Knoten, daher ist die maximale Anzahl von Teilbaumknoten, die an Punkt 3 verwurzelt sind, max_child [3] = 4, min ist unverändert.

Berechnen Sie als nächstes Punkt 1, der sein eigener Knoten ist, plus die Größe seiner Teilbaumpunkte 2 und 3. Der grüne Teil besteht aus zwei Teilbäumen, Größe [2] = 3 Knoten, Größe [3] = 1 Knoten. Der rote Teil ist weg, n-Größe [1] = 0 Knoten. Daher ist die maximale Anzahl von Teilbaumknoten, die an Punkt 1 verwurzelt sind, max_child [1] = 3 und min ist unverändert.

Sobald min aktualisiert ist, wird auch der Schwerpunkt auf den entsprechenden Knoten aktualisiert. Es ist hier einfach nicht geschrieben, Sie können den Code sehen.

Es spielt also keine Rolle, wen Sie zu Beginn dieser Rekursion auswählen. Wenn Sie einen beliebigen Punkt auswählen, können Sie die maximale Anzahl von Teilbaumknoten berechnen, die an jedem Punkt verwurzelt sind, und dann den Schwerpunkt ermitteln.

Der Code zum Ermitteln des Schwerpunkts des Baums lautet wie folgt:

// 全局变量
int n=17; // 所有结点数
int size[n];// 以n为根的树的结点数
int max_child[n];// 以n为根的树的最大子树的结点数
int min;// max_child中最小的那个
int first[n+1],edge[(n-1)*2]// 顶点表,边表
int gravity=0;// 被选为重心的结点

// 传入参数
// start 代表当前结点
// parent 代表当前结点的父结点,这里是为了防止遍历start的子结点的时候把父结点也遍历进去了
void getGravity(int start, int parent)
{
    // size[start]代表当前结点的个数,初始为1是算上本身
    size[start]=1;
    // max_child[start]代表当前结点的最大子树结点数
    max_child[start]=0;
    // 遍历以start结点为起点的所有边(除去连接父结点的边)
    for(int i=first[start]; i; i=edge[i].next)
    {
        // end为当前边的终点(也是start点的子结点)
        int end=edge[i].end;
        // 如果这个终点已经被遍历或者这个终点是父结点,就跳过
        if(visited[end] || end==parent){
            continue;
        }
        // 继续遍历终点的子结点,这里其实就是遍历start的一棵子树的所有结点数
        getGravity(end, start);
        // 遍历完这棵子树的所有结点后,把子树的结点数加起来
        size[start]+=size[end];
        // 如果这颗子树的结点数大于max_child,就更新它
        if(size[end] > max_child[start]){
            max_child[start]=size[end];
        }
    }
    // 上面的循环是用来遍历start的每一棵子树并比较出最大子树结点
    // 接下来就是算“红色部分”,也就是n-size[i]部分的结点数,并比较出最终的最大子树结点
    if(n-size[start] > max_child[start]){
        max_child[start]=n-size[start];
    }
    // 从max_child中比较出最小的,以找出重心
    if(min < max_child[start]){
        min=max_child[x];
        gravity=start;
    }
}

Die Speichermethode für den Baum besteht hier darin, zuerst die Scheitelpunkttabelle zu verwenden, um jeden Punkt zu speichern, und zuerst [i] repräsentiert die Nummer der ersten Kante, die dem Knoten mit der Nummer i entspricht. Jede Zeile der Kantentabellenkante stellt eine Kante dar, einschließlich Startpunkt, Endpunkt, Gewicht dieser Kante und der nächsten Kante mit demselben Startpunkt. Da diese Kanten ungerichtet sind, wird eine Kante zweimal in der Kantentabelle gespeichert.

Der Code zum Hinzufügen von Baumknoten und Kanten lautet wie folgt:

int first[n+1],edge[(n-1)*2]// 顶点表,边表
int num=0;
void addNodeAndEdge(int start,int end,int weight)
{
    num++;// 编号,没错,要从1开始
    edge[num].start=start;// 起点
    edge[num].end=end;// 终点
    edge[num].weight=weight;// 权重
    edge[num].next=first[start];// 下一条相同起点的边
    first[start]=num;// 加入顶点
}

2. Berechnen Sie das Abstandsarray von jedem Punkt zum Schwerpunkt.

Nach Auswahl des Schwerpunkts des Baumes berechnen wir den Abstand (dh das Gewicht) von allen Punkten zu diesem Schwerpunkt ebenfalls mit einer rekursiven Methode. Dies sollte leicht zu verstehen sein, ohne zu viele Erklärungen.

int t=0;
// start是传入的点,parent是start的父结点,weight是start和parent连线的权重
// parent在这里是为了防止遍历start的子结点的时候把父结点也遍历进去了
// 因为每个点到start的距离是自上而下地累加,所以传入weight
// 该递归函数的主要作用是返回每个点到重心的距离,所以一开始调用递归函数的时候
// start默认是重心,parent和weight默认是0。
void getDistance(int start, int parent, int weight)
{
    // dis数组保存了每个点到重心的距离(权重),为什么用t来做下标而不是点的编号呢
    // 因为后面的做法只用数点对的个数,不在乎是谁到谁
    // t是从1开始的
    dis[++t]=weight;
    for(int i=first[start]; i; i=edge[i].next)
    {
        int end=edge[i].end;
        // 如果这个终点已经被遍历或者这个终点是父结点,就跳过
        // 这点很重要,因为如果传入的start不是根结点而是根结点的子树的时候
        // 它就不会把根结点再遍历一次
        if(visited[end] || end==parent){
            continue;
        }
        getDistance(end, start, weight+edge[i].weight);
    }
}

In diesem Schritt erhalten wir das Dis-Array und der Abstand von jedem Punkt zum Schwerpunkt wird in Dis gespeichert. Auch hier hat der Index von dis nichts mit der Knotennummer zu tun. Nehmen Sie das vorherige Beispiel, wie in der folgenden Abbildung gezeigt:

3. Alle Punktpaare, die durch den Schwerpunkt verlaufen, abzüglich aller Punktpaare, die durch die Schwerpunktunterknoten verlaufen, um den zulässigen Punktlogarithmus zu erhalten

Betrachten Sie als nächstes die Anzahl der Pfade, deren Länge kleiner als K ist. Meine erste Idee ist es, die Punkte kleiner oder gleich K in dis zu finden, so dass wir den Punktlogarithmus auswählen, dessen Abstand vom Punkt zum Schwerpunkt kleiner als K ist, und dann den Punktlogarithmus berechnen, der durch den Schwerpunkt verläuft und der Abstand kleiner als K ist. Nach dem zweiten Referenzartikel ist dies jedoch nicht der Fall. Der zweite Referenzartikel im Zitierabschnitt:

Nach (Erhalten des Dis-Arrays) verlaufen die verbundenen Pfade in diesem Teilbaum durch den Schwerpunkt und tragen zur Antwort bei (dh das Punktpaar ( i,j) ( i<j), dessen Abstand kleiner als k ist ),  dis[i]+dis[j] <= K und nach dem Entfernen des Schwerpunkts sind i und j dies  nicht Im selben Unicom-Block .

Aber offensichtlich ist es etwas umständlich, die Bedingung "nicht im selben Verbindungsblock" zu erfüllen. Es gibt also einen kleinen Trick: Unabhängig davon, ob es sich im selben Verbindungsblock befindet oder nicht, berechnen Sie die Anzahl der übereinstimmenden Pfade des aktuellen Baums und erhalten Sie dann Die Anzahl von minus dem Punkt-zu-Pfad-Abstand (vorbei am Schwerpunkt) in dem Teilbaum, der vom untergeordneten Knoten des Schwerpunkts verwurzelt wird, ist kleiner oder gleich der Anzahl von K, und dies ist ausreichend.

Dies bedeutet, dass wir, nachdem wir jetzt den Abstand von jedem Punkt zum Schwerpunkt haben, dis von klein nach groß sortieren (die Sortierung besteht darin, die Punktepaare zu berechnen, die kleiner als K sind), sie paarweise addieren und alle Punkte außer dem Schwerpunkt addieren. Klicken Sie auf alle Kombinationen, z.

dis [2] + dis [5] entspricht 2——1——3;

dis [3] + dis [5] entspricht 4——2——1——3;

Aber es gibt eine Situation, die nicht gut ist, wie dis [3] + dis [4] entspricht 4——2——1——2——5.

Die Punkte 4 und 5 mussten den Schwerpunktpunkt 1 nicht passieren. Wie kann dies beseitigt werden? Finden Sie zuerst die Eigenschaften dieses Punktpaars heraus. Es ist leicht zu erkennen, dass sich diese Punktepaare alle in einem Teilbaum befinden. Sowohl 4 als auch 5 sind über 2 mit dem Schwerpunkt verbunden. Mit anderen Worten, wenn ein Punktpaar den untergeordneten Knoten des Schwerpunkts passiert, sind sie illegal. Mit dieser Urteilsbedingung können wir sie beseitigen.

Nachdem wir die Eliminierungsmethode verstanden haben, sprechen wir nun über die Arbeitsfunktion. Die eingehenden Parameter sind die Entfernung (Gewicht) vom Knotenstart und vom Start zum übergeordneten Knoten.

Es wird verwendet, um alle Kombinationen über den Startpunkt zu berechnen (legal und illegal werden gezählt). In der ersten dfs-Funktion haben wir einen Schwerpunkt. Die Austrittsarbeit wird einmal vom Schwerpunkt aufgerufen, der eingehende Start ist der Schwerpunkt und das Gewicht ist 0, und alle durch den Schwerpunkt verlaufenden Punktpaare werden berechnet. Gehen Sie zurück zu dfs, durchlaufen Sie alle untergeordneten Knoten des Schwerpunkts, rufen Sie die Arbeit separat auf, der eingehende Start ist der untergeordnete Knoten und das Gewicht ist das Gewicht vom untergeordneten Knoten zum Schwerpunkt und berechnen Sie alle Punktpaare, die durch den aktuellen untergeordneten Knoten verlaufen, obwohl dies der Fall ist Nach dem Punktpaar des untergeordneten Knotens liegt der berechnete Abstand noch zum Schwerpunkt. Da es sich um ein Punktpaar handelt, das durch den Schwerpunkt verläuft, oder um ein Punktpaar, das durch den untergeordneten Knoten verläuft, müssen sie mit k verglichen werden. Daher sollte der berechnete Abstand zum Schwerpunkt liegen.

// 传入的start要么是重心,weight=0
// 要么是重心的孩子结点,weight是重心与孩子结点的距离(权重)
int work(int start,int weight) {
    t=0;
    // 如果start是重心,算出 以重心为根的树中的结点 到start的距离,然后两两组合,选出加起来小于k的点对
    // 如果start是重心的孩子结点,算出 以该孩子结点为根 的子树中的结点 到重心的距离,然后两两组合,选出加起来小于k的点对
    // eg:重心是S的孩子结点是a,那么算出的点对数是 以a为根的树 的所有结点 两两组合,但是距离算的是 所有结点 到S的距离,因为仍然要判断小于k,和经过重心的点对要一致
    // getDistance需要传入weight就是为了 重心的孩子结点的 孩子结点的 dis是到重心的距离
    getDistance(start, 0, weight);
    // 得到dis数组后,对其进行从小到大排序
    // 注意,这里的t已经不是0了,它是全局变量,在getDistance里面遍历了start出发的所有结点
    sort(dis+1,dis+1+t);
    // pair_num表示经过重心的点对数量
    int pair_num=0;
    int i=1,j=t;
    // 这个while循环就是把两个dis相加的和小于等于K的点对数量计算出来
    while (i<j){
        while (i<j && dis[i]+dis[j]>K) 
            j--;
        pair_num+=j-i;
        i++;
    }
    return pair_num;
}

4. Wiederholen Sie die Vorgänge 1, 2 und 3 für die untergeordneten Knoten des Schwerpunkts (Rekursion).

Das erste Mal, wenn die dfs-Funktion häufig nach Belieben in einem Knoten ausgeführt wird, um den Schwerpunkt des gesamten Baums zu ermitteln, wie z. B. Punkt 1 in der obigen Abbildung. Hier wird Punkt 1 verwendet, um die Arbeitsfunktion aufzurufen und alle Punktlogarithmen bis Punkt 1 zu finden, einschließlich legaler und illegaler. Überqueren Sie dann die untergeordneten Knoten von Punkt 1 in der for-Schleife, die in der obigen Abbildung die Punkte 2 und 3 sind. Verwenden Sie Punkt 2 und Punkt 3, um work erneut aufzurufen und alle Punktpaare zu finden, die durch die Punkte 2 bzw. 3 verlaufen. Und dann von ans subtrahiert, erhalten Sie ein legales Punktpaar.

Sie könnten denken, dass 4 und 5 Punktpaare sind, die kleiner als k sind, aber wenn Sie sie subtrahieren, wird es verschwinden? Nach dem Subtrahieren werden Punkt 2 und Punkt 3 zur Rekursion an dfs übergeben, und das Paar aus Punkt 4 und Punkt 5 wird gezählt. ans ist eine globale Variable und wird nach dem ersten dfs weiter akkumulieren und subtrahieren.

// 起始点是start,递归调用会dfs所有的结点
void dfs(int start){
    // 以start为起点找到重心
    // 注意,虽然一开始我们说了从树的任何一个点开始遍历都能找出一个确定的重心,
    // 但是这里从start开始,如果它有父结点,就不要再遍历的,只找以它为根的树的重心
    getGravity(start,0);
    // 用这个重心算出所有跨过该重心的路径数
    ans += work(gravity,0);
    // 标记这个重心已访问
    visited[gravity]=1;
    // 从重心开始访问子结点
    for(int i=first[start]; i; i=edge[i].next){
        int end=edge[i].end;
        if (visited[end]){
            continue;
        }
        // 减去以子结点为根的树的所有跨过子结点的路径数
        ans -= work(end, edge[i].weight);
        // 从子结点开始继续递归
        dfs(end);
    }
    return;
}

Dann setzen Sie die Hauptfunktion:

int main()
{
    // 输入结点个数
    scanf("%d",&n);
    // 输入每个结点的信息
    for(int i=1;i<n;i++)
    {
        int start, end, weight;
        scanf("%d %d %d",&start,&end,&weight);
        addNodeAndEdge(start,end,weight);
        addNodeAndEdge(end,start,weight);
    }
    dfs(1);
    printf("%d\n", ans);
    return 0;
}

Schließlich ist der Gesamtcode, der nicht getestet wurde, hauptsächlich darauf zurückzuführen, dass keine Zeit vorhanden ist, aber ich verstehe den Code vollständig und er reicht für den Test.

#include <stdio.h>
#define MAX 10000;

// 全局变量
int n; // 所有结点数
int size[n],max_child[n],min=MAX;// 以n为根的树的结点数,以n为根的树的最大子树的结点数
int first[n+1],edge[(n-1)*2]// 顶点表,边表
int visited[n+1];// 标记已访问过的点
int dis[];// 每个点到重心的距离
int gravity=0;// 重心
int num=0,t=0;// 结点编号
int ans=0;// 最终结果:小于等于K的点对数量

void addNodeAndEdge(int start, int end, int weight)
{
    num++;// 编号
    edge[num].start=start;// 起点
    edge[num].end=end;// 终点
    edge[num].weight=weight;// 权重
    edge[num].next=first[start];// 下一条相同起点的边
    first[start]=num;// 加入顶点
}

// 传入重心,以及重心和它的父结点之间的权重
int work(int start,int weight) {
    t=0;
    // 算出start到各个结点的距离
    getDistance(start, 0, weight);
    // 得到dis数组后,对其进行从小到大排序
    // 注意,这里的t已经不是0了,它是全局变量,在getDistance里面遍历了start出发的所有结点
    sort(dis+1,dis+1+t);
    // pair_num表示点对数量
    int pair_num=0;
    int i=1,j=t;
    // 这个while循环就是把两个dis相加的和小于等于K的点对数量计算出来
    while (i<j){
        while (i<j && dis[i]+dis[j]>K) j--;
        pair_num+=j-i;
        i++;
    }
    return pair_num;
}

void getDistance(int start, int parent, int weight)//fa表示x的父亲,z表示x到目标点的距离
{
    // dis数组保存了每个点到重心的距离(权重),为什么用t来做下标而不是点的编号呢
    // 因为后面的做法只用数点对的个数,不在乎是谁到谁
    dis[++t]=weight;
    for(int i=first[start]; i; i=edge[i].next)
    {
        int end=edge[i].end;
        // 如果这个终点已经被遍历或者这个终点是父结点,就跳过
        // 这点很重要,因为如果传入的start不是根结点而是根结点的子树的时候
        // 它就不会把根结点再遍历一次
        if(visited[end] || end==parent){
            continue;
        }
        getDistance(end, start, weight+edge[i].weight);
    }
}
// 传入参数
// start 代表当前结点
// parent 代表当前结点的父结点,这里是为了防止遍历start的子结点的时候把父结点也遍历进去了
void getGravity(int start, int parent)
{
    // size[start]代表当前结点的个数,初始为1是算上本身
    size[start]=1;
    // max_child[start]代表当前结点的最大子树结点数
    max_child[start]=0;
    // 遍历以start结点为起点的所有边(除去连接父结点的边)
    for(int i=first[start]; i; i=edge[i].next)
    {
        // end为当前边的终点(也是start点的子结点)
        int end=edge[i].end;
        // 如果这个终点已经被遍历或者这个终点是父结点,就跳过
        if(visited[end] || end==parent){
            continue;
        }
        // 继续遍历终点的子结点,这里其实就是遍历start的一棵子树的所有结点数
        getGravity(end, start);
        // 遍历完这棵子树的所有结点后,把子树的结点数加起来
        size[start]+=size[end];
        // 如果这颗子树的结点数大于max_child,就更新它
        if(size[end] > max_child[start]){
            max_child[start]=size[end];
        }
    }
    // 上面的循环是用来遍历start的每一棵子树并比较出最大子树结点
    // 接下来就是算“红色部分”,也就是n-size[i]部分的结点数,并比较出最终的最大子树结点
    if(n-size[start] > max_child[start]){
        max_child[start]=n-size[start];
    }
    // 从max_child中比较出最小的,以找出重心
    if(min < max_child[start]){
        min=max_child[x];
        gravity=start;
    }
}

// 递归地求每个树的经过重心的点对数量
// 起始点是start
void dfs(int start){
    // 以start为起点找到重心
    // 注意,虽然一开始我们说了从树的任何一个点开始遍历都能找出一个确定的重心,
    // 但是这里的意思是,从start开始,它的父结点就不要再遍历的,只找它和它的子树的重心
    getGravity(start,0);
    // 用这个重心算出所有跨过该重心的路径数
    ans += work(gravity,0);
    // 标记这个重心已访问
    visited[gravity]=1;
    // 从重心开始访问子结点
    for(int i=first[start]; i; i=edge[i].next){
        int end=edge[i].end;
        if (visited[end]){
            continue;
        }
        // 减去以子结点为根的树的所有跨过子结点的路径数
        ans -= work(end, edge[i].weight);
        // 以子结点为根的树继续求重心、所有跨过路径数
        dfs(end);
    }
    return;
}

int main()
{
    // 输入结点个数
    scanf("%d",&n);
    // 输入每个结点的信息
    for(int i=1;i<n;i++)
    {
        int start, end, weight;
        scanf("%d %d %d",&start,&end,&weight);
        addNodeAndEdge(start,end,weight);
        addNodeAndEdge(end,start,weight);
    }
    dfs(1);
    printf("%d\n", ans);
    return 0;
}

Die zeitliche Komplexität wird vorübergehend nicht berechnet. . .

Ich denke du magst

Origin blog.csdn.net/qq_33514421/article/details/112379820
Empfohlen
Rangfolge