ตัวค้นหาผลที่ตามมาที่ยาวที่สุดที่เร็วที่สุด


11

งานของคุณคือการแก้ปัญหาที่ยาวที่สุดที่พบบ่อย subsequenceสำหรับnสตริงที่มีความยาว 1,000

วิธีการแก้ปัญหาที่ถูกต้องในการแก้ไขปัญหา LCS สำหรับสองคนหรือมากกว่าสตริงS 1 ... S nใด ๆ สตริงTของความยาวสูงสุดเช่นว่าตัวละครของTปรากฏในทุกS ฉันในการสั่งซื้อเช่นเดียวกับในT

โปรดทราบว่าTไม่ต้องย่อยสตริงของSฉัน

เราได้แก้ไขปัญหานี้แล้วในปริมาณที่สั้นของรหัส เวลานี้ขนาดไม่สำคัญ

ตัวอย่าง

สตริงaxbyczและ xaybzcมี 8 ส่วนประกอบทั่วไปของความยาว 3:

abc abz ayc ayz xbc xbz xyc xyz

สิ่งเหล่านี้จะเป็นทางออกที่ถูกต้องสำหรับปัญหา LCS

รายละเอียด

เขียนโปรแกรมเต็มรูปแบบที่แก้ปัญหา LCS ตามที่อธิบายไว้ข้างต้นปฏิบัติตามกฎต่อไปนี้:

  • ข้อมูลที่ป้อนจะประกอบด้วยสตริงความยาวตั้งแต่สองสตริงขึ้นไป 1,000 ตัวประกอบด้วยอักขระ ASCII พร้อมรหัสจุดระหว่าง 0x30 ถึง 0x3F

  • คุณต้องอ่านอินพุตจาก STDIN

    คุณมีสองตัวเลือกสำหรับรูปแบบอินพุต:

    • แต่ละสตริง (รวมถึงสตริงสุดท้าย) ตามด้วยตัวป้อนบรรทัด

    • สตริงจะถูกผูกมัดพร้อมกับไม่มีตัวคั่นและไม่มีการป้อนบรรทัดต่อท้าย

  • จำนวนสตริงจะถูกส่งผ่านเป็นพารามิเตอร์บรรทัดคำสั่งไปยังโปรแกรมของคุณ

  • คุณต้องเขียนผลลัพธ์เช่นโซลูชันใด ๆ ที่ถูกต้องไปยัง LCS ไปยัง STDOUT แล้วตามด้วยบรรทัดเดียว

  • ภาษาที่คุณเลือกต้องมีคอมไพเลอร์ / ล่าม (เช่นเบียร์) ฟรีสำหรับระบบปฏิบัติการของฉัน (Fedora 21)

  • หากคุณต้องการธงแปลหรือล่ามที่เฉพาะเจาะจงใด ๆ โปรดพูดถึงมันในโพสต์ของคุณ

เกณฑ์การให้คะแนน

ฉันจะเรียกใช้โค้ดของคุณด้วยสายอักขระ 2, 3 และอื่น ๆ จนกว่าจะใช้เวลานานกว่า 120 วินาทีเพื่อพิมพ์โซลูชันที่ถูกต้อง ซึ่งหมายความว่าคุณมี 120 วินาทีสำหรับแต่ละค่าของn

จำนวนสูงสุดของสตริงที่รหัสของคุณเสร็จในเวลาคือคะแนนของคุณ

ในกรณีที่มีคะแนนผูกของnส่งที่แก้ปัญหาสำหรับnสตริงในเวลาอันสั้นจะมีการประกาศผู้ชนะ

การส่งทั้งหมดจะถูกกำหนดเวลาบนเครื่องของฉัน (Intel Core i7-3770, 16 GiB RAM, ไม่มี swap)

nสตริงของ(n-1) วันที่ทดสอบจะถูกสร้างขึ้นโดยการโทรrand n(และปอก linefeeds ที่หากมีการร้องขอ) ที่randถูกกำหนดไว้ดังต่อไปนี้:

rand()
{
    head -c$[500*$1] /dev/zero |
    openssl enc -aes-128-ctr -K 0 -iv $1 |
    xxd -c500 -ps |
    tr 'a-f' ':-?'
}

กุญแจอยู่0ในรหัสข้างต้น แต่ฉันขอสงวนสิทธิ์ในการเปลี่ยนเป็นค่าไม่เปิดเผยถ้าฉันสงสัยว่าใครบางคน (บางส่วน) hardcoding ผลลัพธ์


เราสามารถโยนข้อยกเว้นได้ไหม
HyperNeutrino

@JamesSmith ตราบใดที่เอาต์พุตถูกต้องแน่นอน
เดนนิส

เนื่องจากฉันกำลังอ่านกับ bufferedreader ฉันจะทิ้ง ioexception ได้public static void main(...)ไหม
HyperNeutrino

@JamesSmith ฉันไม่รู้จัก Java ดังนั้นฉันจึงไม่รู้ว่ามันคืออะไร แต่ไม่ต้องกังวลกับข้อยกเว้น
เดนนิส

4
@JamesSmith เนื่องจากความยาวของรหัสไม่สำคัญสำหรับความท้าทายนี้คุณไม่สามารถตรวจจับข้อยกเว้นได้หรือไม่?
Reto Koradi

คำตอบ:


5

C, n = 3 ใน ~ 7 วินาที

ขั้นตอนวิธี

อัลกอริทึมเป็นการวางนัยทั่วไปโดยตรงของโซลูชันการเขียนโปรแกรมแบบไดนามิกมาตรฐานไปยังnลำดับ สำหรับ 2 สายAและBการเกิดซ้ำมาตรฐานมีลักษณะดังนี้:

L(p, q) = 1 + L(p - 1, q - 1)           if A[p] == B[q]
        = max(L(p - 1, q), L(p, q - 1)) otherwise

3 สตริงA, B, Cผมใช้:

L(p, q, r) = 1 + L(p - 1, q - 1, r - 1)                          if A[p] == B[q] == C[r]
           = max(L(p - 1, q, r), L(p, q - 1, r), L(p, q, r - 1)) otherwise

nรหัสใช้ตรรกะนี้ค่าโดยพลการของ

อย่างมีประสิทธิภาพ

ความซับซ้อนของรหัสของฉันคือ O (s ^ n) ด้วยsความยาวของสตริง จากสิ่งที่ฉันพบดูเหมือนว่าปัญหาจะเกิดขึ้นกับ NP ดังนั้นแม้ว่าอัลกอริทึมที่โพสต์จะไม่มีประสิทธิภาพมากสำหรับค่าที่มีขนาดใหญ่ขึ้นnแต่จริงๆแล้วมันอาจเป็นไปไม่ได้ที่จะทำให้ดีขึ้นอย่างหนาแน่น สิ่งเดียวที่ฉันเห็นคือแนวทางที่ปรับปรุงประสิทธิภาพสำหรับตัวอักษรขนาดเล็ก เนื่องจากตัวอักษรมีขนาดเล็กพอสมควร (16) ซึ่งอาจนำไปสู่การปรับปรุง ฉันยังคงคาดการณ์ว่าจะไม่มีใครพบทางออกที่ถูกกฎหมายที่สูงกว่าn = 4ใน 2 นาทีและn = 4ดูทะเยอทะยานแล้ว

ฉันลดการใช้หน่วยความจำในการเริ่มต้นใช้งานเพื่อให้สามารถจัดการกับn = 4เวลาที่กำหนด แต่มันสร้างความยาวของลำดับเท่านั้นไม่ใช่ลำดับเอง ตรวจสอบประวัติการแก้ไขของโพสต์นี้เพื่อดูรหัสนั้น

รหัส

เนื่องจากลูปบนเมทริกซ์ n-Dim ต้องการตรรกะมากกว่าลูปคงที่ฉันใช้ลูปคงที่สำหรับมิติต่ำสุดและใช้ตรรกะลูปทั่วไปสำหรับมิติที่เหลือเท่านั้น

#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define N_MAX 4

int main(int argc, char* argv[]) {
    int nSeq = argc - 1;
    if (nSeq > N_MAX) {
        nSeq = N_MAX;
    }

    char** seqA = argv + 1;

    uint64_t totLen = 1;
    uint64_t lenA[N_MAX] = {0};
    uint64_t offsA[N_MAX] = {1};
    uint64_t offsSum = 0;
    uint64_t posA[N_MAX] = {0};

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        lenA[iSeq] = strlen(seqA[iSeq]);
        totLen *= lenA[iSeq] + 1;

        if (iSeq + 1 < nSeq) {
            offsA[iSeq + 1] = totLen;
        }

        offsSum += offsA[iSeq];
    }

    uint16_t* mat = calloc(totLen, 2);
    uint64_t idx = offsSum;

    for (;;) {
        for (uint64_t pos0 = 0; pos0 < lenA[0]; ++pos0) {
            char firstCh = seqA[0][pos0];
            int isSame = 1;
            uint16_t maxVal = mat[idx - 1];

            for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
                char ch = seqA[iSeq][posA[iSeq]];
                isSame &= (ch == firstCh);

                uint16_t val = mat[idx - offsA[iSeq]];
                if (val > maxVal) {
                    maxVal = val;
                }
            }

            if (isSame) {
                mat[idx] = mat[idx - offsSum] + 1;
            } else {
                mat[idx] = maxVal;
            }

            ++idx;
        }

        idx -= lenA[0];

        int iSeq = 1;
        while (iSeq < nSeq && posA[iSeq] == lenA[iSeq] - 1) {
            posA[iSeq] = 0;
            idx -= (lenA[iSeq] - 1) * offsA[iSeq];
            ++iSeq;
        }
        if (iSeq == nSeq) {
            break;
        }

        ++posA[iSeq];
        idx += offsA[iSeq];
    }

    int score = mat[totLen - 1];

    char* resStr = malloc(score + 1);
    resStr[score] = '\0';

    for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
        posA[iSeq] = lenA[iSeq] - 1;
    }

    idx = totLen - 1;
    int resIdx = score - 1;

    while (resIdx >= 0) {
        char firstCh = seqA[0][posA[0]];
        int isSame = 1;
        uint16_t maxVal = mat[idx - 1];
        int maxIdx = 0;

        for (int iSeq = 1; iSeq < nSeq; ++iSeq) {
            char ch = seqA[iSeq][posA[iSeq]];
            isSame &= (ch == firstCh);

            uint16_t val = mat[idx - offsA[iSeq]];
            if (val > maxVal) {
                maxVal = val;
                maxIdx = iSeq;
            }
        }

        if (isSame) {
            resStr[resIdx--] = firstCh;
            for (int iSeq = 0; iSeq < nSeq; ++iSeq) {
                --posA[iSeq];
            }
            idx -= offsSum;
        } else {
            --posA[maxIdx];
            idx -= offsA[maxIdx];
        }
    }

    free(mat);

    printf("score: %d\n", score);
    printf("%s\n", resStr);

    return 0;
}

คำแนะนำสำหรับการวิ่ง

วิ่ง:

  • lcs.cบันทึกรหัสในไฟล์เช่น
  • รวบรวมกับตัวเลือกการเพิ่มประสิทธิภาพสูง ฉันใช้:

    clang -O3 lcs.c
    

    บน Linux ฉันจะลอง:

    gcc -Ofast lcs.c
    
  • รันด้วยลำดับ 2 ถึง 4 ที่กำหนดไว้เป็นอาร์กิวเมนต์บรรทัดคำสั่ง:

    ./a.out axbycz xaybzc
    

    เสนอราคาอาร์กิวเมนต์บรรทัดคำสั่งเดี่ยวหากจำเป็นเนื่องจากตัวอักษรที่ใช้สำหรับตัวอย่างมีอักขระพิเศษของเชลล์

ผล

test2.shและtest3.shเป็นลำดับการทดสอบจากเดนนิส ฉันไม่ทราบผลลัพธ์ที่ถูกต้อง แต่ผลลัพธ์ดูน่าเชื่อถืออย่างน้อย

$ ./a.out axbycz xaybzc
score: 3
abc

$ time ./test2.sh 
score: 391
16638018802020>3??3232270178;47240;24331395?<=;99=?;178675;866002==23?87?>978891>=9<6<9381992>>7030829?255>6804:=3>:;60<9384=081;0:?66=51?0;5090724<85?>>:2>7>3175?::<9199;5=0:494<5<:7100?=95<91>1887>33>67710==;48<<327::>?78>77<6:2:02:<7=5?:;>97<993;57=<<=:2=9:8=118563808=962473<::8<816<464<1==925<:5:22?>3;65=>=;27?7:5>==3=4>>5>:107:20575347>=48;<7971<<245<??219=3991=<96<??735698;62?<98928

real  0m0.012s
user  0m0.008s
sys   0m0.003s

$ time ./test3.sh 
score: 269
662:2=2::=6;738395=7:=179=96662649<<;?82?=668;2?603<74:6;2=04=>6;=6>=121>1>;3=22=3=3;=3344>0;5=7>>7:75238;559133;;392<69=<778>3?593?=>9799<1>79827??6145?7<>?389?8<;;133=505>2703>02?323>;693995:=0;;;064>62<0=<97536342603>;?92034<?7:=;2?054310176?=98;5437=;13898748==<<?4

real  0m7.218s
user  0m6.701s
sys   0m0.513s

ขออภัยหากยังไม่ชัดเจน แต่คุณต้องพิมพ์ LCS ไม่ใช่แค่ความยาว
เดนนิส

@ เดนนิสฉันเห็น การเพิ่มประสิทธิภาพบางอย่างของฉันอยู่ในไร้สาระแล้ว ฉันจะต้องกลับไปที่เวอร์ชันที่เก็บเมทริกซ์แบบเต็มเพื่อที่ฉันจะสามารถสร้างสตริงใหม่ได้ สิ่งนี้จะไม่สามารถเรียกใช้งานได้สำหรับ n = 4 แต่ควรจะยังคงเสร็จสิ้นให้ต่ำกว่า 10 วินาทีสำหรับ n = 3 ฉันคิดว่าฉันประมาณ 6-7 วินาทีเมื่อฉันยังมีเมทริกซ์เต็ม
Reto Koradi

อีกครั้งขอโทษ คำถามไม่ชัดเจนเกี่ยวกับเรื่องนี้ ... เมื่อคุณโพสต์ผลลัพธ์ของคุณฉันจะสามารถเปรียบเทียบกับของ BrainSteel ได้ ความยาวของรายงานโปรแกรมของคุณเกินความยาวของผลลัพธ์ของเขาโดย 5 สำหรับ n = 2 โดยวิธีการที่ฉันต้องกำหนดN_MAXเป็นแมโครและเพิ่มธง-std=c99รวบรวมเพื่อรวบรวมรหัสของคุณด้วย GCC
เดนนิส

@Dennis ไม่มีปัญหา มันบอกว่าการแก้ปัญหา "เป็นสตริง" ดังนั้นควรมีความชัดเจนเพียงพอ ฉันเกือบจะใช้ C ++ เป็นพิเศษดังนั้นฉันจึงไม่แน่ใจว่าสิ่งที่ได้รับอนุญาตใน C รหัสนี้เริ่มต้นจาก C ++ แต่เมื่อฉันรู้ว่าฉันไม่ได้ใช้คุณสมบัติ C ++ จริงๆฉันเปลี่ยนมันเป็น C clang บน Mac ของฉัน มีความสุขกับมัน แต่อาจใช้รุ่น C อื่นตามค่าเริ่มต้นหรือผ่อนปรนมากกว่า
Reto Koradi

1
@Dennis Ok ฉันเพิ่มตรรกะการสืบค้นกลับเพื่อให้ฉันสามารถสร้างสตริง ใช้เวลาประมาณ 7 วินาทีในขณะนี้สำหรับ n = 3
Reto Koradi

3

ปัจจุบันคำตอบนี้ใช้งานไม่ได้เนื่องจากข้อบกพร่อง กำลังแก้ไขเร็ว ๆ นี้ ...

C, 2 สายอักขระใน ~ 35 วินาที

นี่เป็นงานที่กำลังดำเนินอยู่ (ตามที่แสดงโดยความยุ่งเหยิงที่น่ากลัว) แต่หวังว่ามันจะทำให้เกิดคำตอบที่ดี!

รหัส:

#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "time.h"

/* For the versatility */
#define MIN_CODEPOINT 0x30
#define MAX_CODEPOINT 0x3F
#define NUM_CODEPOINT (MAX_CODEPOINT - MIN_CODEPOINT + 1)
#define CTOI(c) (c - MIN_CODEPOINT)

#define SIZE_ARRAY(x) (sizeof(x) / sizeof(*x))

int LCS(char** str, int num);
int getshared(char** str, int num);
int strcount(char* str, char c);

int main(int argc, char** argv) {
    char** str = NULL;
    int num = 0;
    int need_free = 0;
    if (argc > 1) {
        str = &argv[1];
        num = argc - 1;
    }
    else {
        scanf(" %d", &num);
        str = malloc(sizeof(char*) * num);
        if (!str) {
            printf("Allocation error!\n");
            return 1;
        }

        int i;
        for (i = 0; i < num; i++) {
            /* No string will exceed 1000 characters */
            str[i] = malloc(1001);
            if (!str[i]) {
                printf("Allocation error!\n");
                return 1;
            }

            scanf(" %1000s", str[i]);

            str[i][1000] = '\0';
        }

        need_free = 1;
    }

    clock_t start = clock();

    /* The call to LCS */
    int size = LCS(str, num);

    double dt = ((double)(clock() - start)) / CLOCKS_PER_SEC;
    printf("Size: %d\n", size);
    printf("Elapsed time on LCS call: %lf s\n", dt);

    if (need_free) {
        int i;
        for (i = 0; i < num; i++) {
            free(str[i]);
        }
        free(str);
    }

    return 0;
}

/* Some terribly ugly global variables... */
int depth, maximum, mapsize, lenmap[999999][2];
char* (strmap[999999][20]);
char outputstr[1000];

/* Solves the LCS problem on num strings str of lengths len */
int LCS(char** str, int num) {
    /* Counting variables */
    int i, j;

    if (depth + getshared(str, num) <= maximum) {
        return 0;
    }

    char* replace[num];
    char match;
    char best_match = 0;
    int earliest_set = 0;
    int earliest[num];
    int all_late;
    int all_early;
    int future;
    int best_future = 0;
    int need_future = 1;

    for (j = 0; j < mapsize; j++) {
        for (i = 0; i < num; i++)
            if (str[i] != strmap[j][i])
                break;
        if (i == num) {
            best_match = lenmap[j][0];
            best_future = lenmap[j][1];
            need_future = 0;
            if (best_future + depth < maximum || !best_match)
                goto J;
            else {
                match = best_match;
                goto K;
            }
        }
    }

    for (match = MIN_CODEPOINT; need_future && match <= MAX_CODEPOINT; match++) {

    K:
        future = 1;
        all_late = earliest_set;
        all_early = 1;
        for (i = 0; i < num; replace[i++]++) {
            replace[i] = strchr(str[i], match);
            if (!replace[i]) {
                future = 0;
                break;
            }

            if (all_early && earliest_set && replace[i] - str[i] > earliest[i])
                all_early = 0;
            if (all_late && replace[i] - str[i] < earliest[i])
                all_late = 0;
        }
        if (all_late) {
            future = 0;
        }

    I:
        if (future) {
            if (all_early || !earliest_set) {
                for (i = 0; i < num; i++) {
                    earliest[i] = (int)(replace[i] - str[i]);
                }
            }

            /* The recursive bit */
            depth++;
            future += LCS(replace, num);
            depth--;

            best_future = future > best_future ? (best_match = match), future : best_future;
        }
    }

    if (need_future) {
        for (i = 0; i < num; i++)
            strmap[mapsize][i] = str[i];
        lenmap[mapsize][0] = best_match;
        lenmap[mapsize++][1] = best_future;
    }

J:
    if (depth + best_future >= maximum) {
        maximum = depth + best_future;
        outputstr[depth] = best_match;
    }

    if (!depth) {
        mapsize = 0;
        maximum = 0;
        puts(outputstr);
    }

    return best_future;
}

/* Return the number of characters total (not necessarily in order) that the strings all share */
int getshared(char** str, int num) {
    int c, i, tot = 0, min;
    for (c = MIN_CODEPOINT; c <= MAX_CODEPOINT; c++) {
        min = strcount(str[0], c);
        for (i = 1; i < num; i++) {
            int count = strcount(str[i], c);
            if (count < min) {
                min = count;
            }
        }
        tot += min;
    }

    return tot;
}

/* Count the number of occurrences of c in str */
int strcount(char* str, char c) {
    int tot = 0;
    while ((str = strchr(str, c))) {
        str++, tot++;
    }
    return tot;
}

ฟังก์ชั่นที่เกี่ยวข้องที่จะดำเนินการทั้งหมดของการคำนวณ LCS LCSคือฟังก์ชั่น รหัสด้านบนจะใช้เวลาในการเรียกใช้ฟังก์ชันนี้

บันทึกเป็นmain.cและรวบรวมด้วย:gcc -Ofast main.c -o FLCS

โค้ดสามารถรันด้วยอาร์กิวเมนต์บรรทัดคำสั่งหรือผ่าน stdin เมื่อใช้ stdin คาดว่าจะมีสตริงจำนวนหนึ่งตามด้วยสตริงต่างๆ

~ Me$ ./FLCS "12345" "23456"
2345
Size: 4
Elapsed time on LCS call: 0.000056 s

หรือ:

~ Me$ ./FLCS
6 
2341582491248123139182371298371239813
2348273123412983476192387461293472793
1234123948719873491234120348723412349
1234129388234888234812834881423412373
1111111112341234128469128377293477377
1234691237419274737912387476371777273
1241231212323
Size: 13
Elapsed time on LCS call: 0.001594 s

ในกล่อง Mac OS X ที่มี 1.7Ghz Intel Core i7 และกรณีทดสอบที่ Dennis จัดหามาเราจะได้ผลลัพธ์ต่อไปนี้สำหรับ 2 สาย:

16638018800200>3??32322701784=4240;24331395?<;=99=?;178675;866002==23?87?>978891>=9<66=381992>>7030829?25265804:=3>:;60<9384=081;08?66=51?0;509072488>2>924>>>3175?::<9199;330:494<51:>748571?153994<45<??20>=3991=<962508?7<2382?489
Size: 386
Elapsed time on LCS call: 33.245087 s

วิธีการที่จะคล้ายกับวิธีการของฉันจะเป็นความท้าทายที่ก่อนหน้านี้ที่นี่ นอกเหนือจากการปรับให้เหมาะสมก่อนหน้านี้ตอนนี้เราตรวจสอบจำนวนอักขระที่แชร์ทั้งหมดระหว่างสตริงที่การเรียกซ้ำทุกครั้งและออกก่อนกำหนดหากไม่มีวิธีรับสตริงย่อยที่ยาวกว่าที่มีอยู่แล้ว

สำหรับตอนนี้มันจัดการ 2 สายโอเค แต่มีแนวโน้มที่จะได้รับความผิดพลาด -y เพิ่มเติม การปรับปรุงเพิ่มเติมและคำอธิบายที่ดีกว่าที่จะมา!


1
ฉันคิดว่าฉันพลาดอะไรบางอย่างไป ด้วย 2 สายไม่ใช่ปัญหาการเขียนโปรแกรมแบบไดนามิกที่ควรใช้เวลาประมาณ 1,000 ^ 2 ขั้นตอนในการแก้ปัญหาหรือไม่ กล่าวอีกนัยหนึ่งเสี้ยววินาที

@ Lembik ใช่มันควร วิธีนี้ถูกสร้างขึ้นสำหรับการจัดการมากกว่า 2 สาย แต่จบลงด้วยการปรับขนาดต่ำเกินไปด้วยความยาวสายอักขระเพื่อให้ได้ผลลัพธ์ที่ดี ฉันมีลูกเล่นอื่น ๆ อีกมากมายที่แขนเสื้อของฉันและถ้ามีพวกเขาทำงานจริง ๆ ... สิ่งที่ควรปรับปรุงอย่างมาก
BrainSteel

ดูเหมือนว่าจะมีปัญหาที่ไหนสักแห่ง รหัสของ @ @ RetoKoradi ค้นหาสตริงย่อยทั่วไปที่ถูกต้องมีความยาว 391 สำหรับ n = 2 ในขณะที่รหัสของคุณรายงานความยาว 386 และพิมพ์สตริงที่มีความยาว 229
Dennis

@Dennis Umm ... ใช่ใช่ใช่แล้ว ... ที่รัก นี่มันน่าอาย ฉันกำลังทำอยู่ :) ฉันจะแก้ไขคำตอบเพื่อสะท้อนข้อผิดพลาด
BrainSteel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.