Cân bằng chân đế


24

Mục tiêu của bạn: Đưa ra một chuỗi dấu ngoặc, xuất ra Khoảng cách tối thiểu Damerau-Levenshtein cần thiết để biến chuỗi đầu vào thành một chuỗi trong đó các dấu ngoặc được cân bằng.

Đầu vào

Chuỗi đầu vào sẽ chỉ chứa dấu ngoặc và không có ký tự nào khác. Đó là, nó là sự kết hợp của bất kỳ nhân vật nào trong đó (){}[]<>. Bạn có thể lấy đầu vào là một chuỗi hoặc một mảng các ký tự. Bạn không thể đưa ra bất kỳ giả định nào khác về chuỗi đầu vào; nó có thể dài tùy ý (tối đa kích thước tối đa được hỗ trợ bởi ngôn ngữ của bạn), nó có thể trống, dấu ngoặc có thể đã được cân bằng, v.v.

Khoảng cách Damerau-Levenshtein

Khoảng cách Damerau-Levenshtein giữa hai chuỗi là số lần chèn, xóa, thay thế ký tự đơn và hoán vị (hoán đổi) tối thiểu của hai ký tự liền kề.

Đầu ra

Đầu ra phải là Khoảng cách tối thiểu của Damerau-Levenshtein giữa chuỗi đầu vào và chuỗi trong đó dấu ngoặc được khớp. Đầu ra phải là một số , không phải là chuỗi cân bằng kết quả.

Một cặp ngoặc được coi là "khớp" nếu dấu ngoặc mở và đóng theo đúng thứ tự và không có ký tự bên trong chúng, chẳng hạn như

()
[]{}

Hoặc nếu mọi yếu tố phụ bên trong nó cũng được khớp.

[()()()()]
{<[]>}
(()())

Các yếu tố phụ cũng có thể được lồng sâu một vài lớp.

[(){<><>[()]}<>()]
<[{((()))}]>

(Cảm ơn @DJMcMayhem cho định nghĩa)

Các trường hợp thử nghiệm

Input                   Possible Balanced       Output

Empty                   Empty                   0
[](){}<>                [](){}<>                0           
[(){}<>                 [(){}<>]                1           
[(])                    []()                    1           
[[[[[[[[                [][][][]                4
(](<>}[>(}>><(>(({}]    ()(<>)[(<><>){}]        7
>]{])<                  []{()}                  3
([)}}>[                 (){}<>                  4
{<((<<][{{}>[<)         <>(<<[]>{}>[])          5
{><({((})>}}}{(}}       {<><({()})>}{}{()}      4
(](<)>}[>(}>>{]<<(]]    (<()<><<>()>>[])<()>    9
}})(                    {}()                    2

(Cảm ơn @WheatWizard đã giải quyết một nửa các trường hợp thử nghiệm)

Đây là , ít byte thắng nhất!

Bài nộp của bạn phải có thể kiểm tra được, có nghĩa là nó sẽ xuất kết quả cho mỗi trường hợp kiểm tra trong không quá một giờ.


9
Cân bằng dấu ngoặc của riêng bạn: P
Christopher

3
Tôi sẽ ngạc nhiên nếu chúng ta thậm chí sẽ thấy một câu trả lời không chính xác duy nhất cho thử thách này.
orlp

5
@SIGSEGV câu trả lời cho câu hỏi đó là 1. Không quan trọng bạn cân bằng nó như thế [<>]hay []<>hay<>
Nathan Merrill

3
@Bijan Nah, nó sẽ không làm cho nó dễ dàng hơn nhiều, và bên cạnh đó, sinh nhật của Brain-Flak sắp diễn ra!
Pavel

3
@qwr Tại sao có giới hạn? Giới hạn thời gian chỉ áp dụng cho các trường hợp thử nghiệm được cung cấp, đối với các đầu vào lớn, chương trình của bạn có thể mất toàn bộ thời gian trên thế giới.
Pavel

Câu trả lời:


13

Võng mạc, 254 252 264 248 240 232 267 byte

Cảm ơn bạn @AnthonyPham, @officialaimm và @MistahFiggins vì đã chỉ ra lỗi

T`[]()`:;'"
+`'-*"|:-*;|{-*}|<-*>
-
+`'(\W+)"|:(\W+);|{(\W+)}|<(\W+)>
A$1$2$3$+B
+`'(\D+)"|:(\D+);|{(\D+)}|<(\D+)>
6$1$2$3$+9
(.*)(}{|"'|;:|><)
1$1
-

A6B9|6A9B
1
A6+B9+|A6+.B9+.|A+6.B+9
11
T`':{";}`<<<>
(.*)(<\W|\W>)
1$1
+`<(.*A.*B.*)?\W|\W(.*A.*B.*)?>
1$1$2
\W|6B|1

Hãy thử trực tuyến!

Giải pháp không vũ phu! Nó hoạt động cho tất cả các trường hợp thử nghiệm, và thậm chí tìm thấy một lỗi trong một.

-2 byte nhờ @MartinEnder ( ${4}đến $+)

+12 byte để giải thích cho các trường hợp hoán đổi bổ sung

-16 byte bằng cách sử dụng tốt hơn các lớp ký tự

-8 byte bằng cách loại bỏ một hạn chế không cần thiết trong việc hoán đổi. Điều này cũng đã sửa một lỗi :)

-10 byte bằng cách kết hợp logic hoán đổi thành một biểu thức chính

+2 byte để chiếm các giao dịch hoán đổi liên tiếp

+ nhiều bản sửa lỗi khác nhau **

Giải trình:

T`[]()`:;'"được sử dụng để thay thế các loại khung đặc biệt cho thuận tiện. Đầu tiên, chúng tôi thay thế đệ quy tất cả các dấu ngoặc khớp bằng -,AB hoặc 69tùy thuộc vào việc chúng có liền kề hay không.

Sau đó, "hoán đổi" hữu ích được thực hiện bằng cách xóa dấu ngoặc vừa khớp và thêm 1 vào đầu chuỗi. Chúng tôi cũng thay thế -bằng chuỗi rỗng, vì nó chỉ được sử dụng cho việc hoán đổi ở trên.

Tiếp theo, chúng tôi thử "thay thế" bằng cách xóa các cặp dấu ngoặc không trùng nhau không trùng với dấu ngoặc đã khớp và thêm 1 chuỗi vào chuỗi.

Cuối cùng, \W|6B|1 đếm bất kỳ dấu ngoặc đơn còn lại cộng với số 1s.

** Tôi hiện đang làm việc trên một phiên bản ngắn hơn sử dụng các tính năng chia dòng của Retina, mặc dù tôi gặp phải một vấn đề đáng kể nên có thể mất nhiều thời gian.


Điều đó ${4}có thể được rút ngắn để $+. Tôi rất tò mò tại sao điều này được đảm bảo để làm việc mặc dù.
Martin Ender

@MartinEnder Cảm ơn! Tôi không chắc chắn rằng nó luôn hoạt động, nhưng nó hoạt động ít nhất cho các trường hợp thử nghiệm được cung cấp và một vài trường hợp cạnh mà tôi đã nghĩ ra
toán rác

2
Cho trước [{][}] [] [[][][][][][]] [][][][][][][][][][][][], bạn có thể chỉ cần trao đổi }bên trong cặp dấu ngoặc đơn đầu tiên và cân bằng nó. Giãn cách được sử dụng để làm cho đầu vào dễ đọc hơn một chút. Bạn đã xuất 3 nhưng nó thực sự nên là một.
Anthony Phạm

@AnthonyPham Cảm ơn! Tôi nghĩ tôi biết tại sao điều đó không hiệu quả, tôi sẽ cố gắng tìm ra một cách thông minh để khắc phục nó
nghiện toán học

Thậm chí kỳ lạ là [{]}trả về 1 nhưng [{][]}trả về 2.
Anthony Phạm

12

Brain-Flak , 1350 byte

{({}(())(<>))<>({(()()()())<{({}[()])<>}{}>}{}<>({<({}[()])>{()(<{}>)}}{}{}<>))<>}<>([[]]){([[]({}()<>)]<>)<>{(({}())<<>(({})<(({}(<()>))<>({}))([(())()()]){<>({}())}{}{<>{}<>({}()){(((({}<(({}<>)<{({}()<([(){}])>)}{}>)<>(({}(<>))<{({}()<([(){}])>)}{}<>>)><>({}))(<(((({}({})[()])[()()]<>({}))<>[({})({}){}]({}<>))<>[(({}<>)<>({}<>)<>)])<>>)))[()](<()>)<<>(({})<({}{}()){({}()<({}<>)<>>)}{}<>(({})<<>(({}<>))>)<>(())>){({}[()()]<(<([({[{}]<(({})()<>[({})]<>)>{()(<{}>)}}{}<(({})<>[()({}<(({}<<>({}<>)<>(({})<>)>)<>[(){}])<>>)]<>)>{()(<{}>)}{}(){[()](<{}>)}<<>{({}<>)<>}{}>)]({}{}))>)<>{({}<>)<>}>)}{}{}<>{}{}{({}<>)<>}{}{}(<>)<>{({}<>)<>}{}{(<{}>)<>{({}<>)<>}<>({}<{}>){({}<>)<>}}{}((({}<({}({})({})<{{}<>{}(<>)}{}(((({}<({}<>)>)<>)))<>>)<>>)<><({}<({}<<>(()())>)>)>)<<>({}<{}{({}<>)([()()()]){((({}()()<>))[()]<(({()(<{}>)}{})<>({}<(({}<<>({}[()()](()[({})({})]({[()](<{}>)}{}<>{}<(({})<>)>)<>))>)<>)>)<>)<>({}<({}<({}<({}<>)>)>)>)>)}{}{}<>}<>{}{}{}{}{}{}{}{}>)>)>)}{}({}<({}<{({}<(({}){({}())}{}{}<(({}){({}())}{}{}<>)>)>)<>}<>{((({}(()()){([{}](<({}(<()>)<>){({}<({}<>)>(())<>)}{}>({})<<>{{}({}<>)<>}{}>))([{}()]{})}{})))<>(({}))<>{<>({}[()])}{}({}<<>{}{}{<>}>)<>{}}<>(({}<>){[()](<{}>)}{})(<>)>)>)<>(<({}<>)>)<>}<>{}({}<(({}){({}())}{}{}){({}<({}<>)>(())<>)}{}{}>)<>{{}({}<>)<>}{}>)<>>)}{}<>([[]{}])}{}(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)

Hãy thử trực tuyến!

Với các so sánh tốc độ không đổi và hội thảo con trỏ, thuật toán này là O (n 3 ). Thật không may, Brain-Flak không có cái nào trong số này, vì vậy chương trình này chạy trong O (n 5 thời gian ). Trường hợp thử nghiệm dài nhất mất khoảng 15 phút.

Đơn giản hóa kết quả

Để thấy rằng thuật toán của tôi hoạt động, chúng tôi cần hiển thị một số kết quả làm giảm đáng kể không gian tìm kiếm. Những kết quả này dựa trên thực tế rằng mục tiêu là toàn bộ ngôn ngữ thay vì chỉ một chuỗi cụ thể.

  • Không cần chèn thêm. Thay vào đó, bạn chỉ có thể loại bỏ dấu ngoặc mà ký tự được chèn cuối cùng sẽ khớp.

  • Bạn sẽ không bao giờ cần phải xóa một khung, sau đó trao đổi hai hàng xóm của nó. Để thấy điều này, giả sử wlog rằng khung bị loại bỏ (, vì vậy chúng tôi đang chuyển đổi a(cthành cahai bước. Bằng cách thay đổi cvà chèn một bản sao, chúng ta có thể đạt được ca()hai bước mà không cần trao đổi. (Việc chèn này sau đó có thể được loại bỏ theo quy tắc trên.)

  • Cùng một khung sẽ không bao giờ cần phải hoán đổi hai lần. Đây là một thực tế tiêu chuẩn về khoảng cách Damerau-Levenshtein nói chung.

Một kết quả đơn giản hóa khác mà tôi không sử dụng, vì kế toán cho chúng sẽ tốn byte:

  • Nếu hai dấu ngoặc được hoán đổi và chúng không khớp với nhau, thì kết quả khớp với từng dấu ngoặc đó sẽ không bao giờ bị thay đổi hoặc hoán đổi.

Thuật toán

Khi bất kỳ chuỗi nào được giảm xuống thành một chuỗi cân bằng, một trong những điều sau đây sẽ là đúng:

  • Dấu ngoặc đầu tiên bị xóa.
  • Giá đỡ đầu tiên giữ nguyên vị trí và khớp với giá đỡ tại một số vị trí k(có thể sau khi thay đổi một hoặc cả hai).
  • Khung đầu tiên được hoán đổi với khung thứ hai, lần lượt khớp với khung ở vị trí k.

Trong trường hợp thứ hai, khung ở vị trí kcó thể đã hoán đổi với một trong những hàng xóm của nó. Trong một trong hai trường hợp sau, chuỗi giữa khung đầu tiên (có thể là mới) và khung bắt đầu ở vị trí kphải được chỉnh sửa thành chuỗi cân bằng, cũng như chuỗi bao gồm mọi thứ sau đó k.

Điều này có nghĩa là một phương pháp lập trình động có thể được sử dụng. Do một khung bị tráo đổi không cần phải hoán đổi một lần nữa, chúng ta chỉ cần xem xét các chuỗi con liền kề, cũng như các chuỗi được hình thành bằng cách loại bỏ ký tự thứ hai và / hoặc ký tự áp chót khỏi chuỗi con đó. Do đó, chỉ có các phần sau O (n 2 ) mà chúng ta cần xem xét. Mỗi trong số đó có O (n) các cách có thể để khớp (hoặc xóa) dấu ngoặc đầu tiên, vì vậy thuật toán sẽ là O (n 3 ) theo các điều kiện trên.

Cấu trúc dữ liệu

Ngăn xếp bên phải bao gồm các dấu ngoặc từ chuỗi gốc, với hai byte cho mỗi dấu ngoặc. Mục nhập đầu tiên xác định toàn bộ dấu ngoặc và được chọn sao cho dấu ngoặc khớp có chênh lệch chính xác là 1. Mục nhập thứ hai chỉ xác định xem đó là dấu ngoặc mở hay dấu ngoặc đóng: điều này xác định có bao nhiêu thay đổi cho hai dấu ngoặc khớp lẫn nhau. Không có số không ẩn nào dưới đây được bao giờ rõ ràng, để chúng ta có thể sử dụng[] để có được tổng chiều dài của chuỗi này.

Mỗi chuỗi con được xem xét được biểu thị bằng hai số trong phạm vi 0 đến 2n: một cho vị trí bắt đầu và một cho cuối. Giải thích như sau:

  • Một chuỗi con bắt đầu tại 2ksẽ bắt đầu tại vị trí k(được lập chỉ mục 0) và ký tự thứ hai không bị xóa.
  • Một chuỗi con bắt đầu tại 2k+1sẽ bắt đầu tại vị trík và ký tự thứ hai bị xóa do bị tráo đổi sang trái.
  • Một chuỗi con kết thúc tại 2ksẽ kết thúc ngay trước vị trí k(nghĩa là phạm vi được bao gồm bên trái và không bao gồm bên phải.)
  • Một chuỗi con kết thúc tại 2k-1sẽ kết thúc ngay trước vị trí kvà ký tự áp chót bị loại bỏ do bị tráo đổi phải.

Một số phạm vi ( kđến k+1, 2k+1đến 2k+1, 2k+1đến 2k+32k+1đến 2k+5) không có ý nghĩa vật lý. Một số trong số đó hiển thị dưới dạng giá trị trung gian, vì nó dễ hơn việc thêm các kiểm tra bổ sung để tránh chúng.

Ngăn xếp bên trái lưu trữ số lượng chỉnh sửa cần thiết để chuyển đổi từng chuỗi con thành một chuỗi cân bằng. Khoảng cách chỉnh sửa cho khoảng thời gian (x,y)được lưu trữ ở độ sâu x + y(y-1)/2.

Trong vòng lặp bên trong, các mục được thêm vào phía trên ngăn xếp bên trái để biểu thị việc di chuyển nào là có thể. Các mục này dài 5 byte. Đếm từ trên xuống, những con số d+1, y1, x1, y2, x2, nơi di chuyển chi phí dsửa bước và chia các chuỗi vào (x1,y1)(x2,y2).

Mật mã

Mô tả sắp tới. Bây giờ, đây là bản sao làm việc của tôi về mã. Một số ý kiến ​​có thể không phù hợp với thuật ngữ.

# Determine bracket type for each byte of input
{({}(())(<>))<>({(()()()())<{({}[()])<>}{}>}{}<>({<({}[()])>{()(<{}>)}}{}{}<>))<>}

# For every possible interval length:
<>([[]]){

  # Compute actual length
  ([[]({}()<>)]<>)

  # Note: switching stacks in this loop costs only 2 bytes.
  # For each starting position:
  # Update/save position and length
  <>{(({}())<<>(({})<

    # Get endpoints
    (({}(<()>))<>({}))

    # If length more than 3:
    ([(())()()]){<>({}())}{}{

      # Clean up length-3 left over from comparison
      <>{}<>

      # Initialize counter at 2
      # This counter will be 1 in the loop if we're using a swap at the beginning, 0 otherwise
      ({}())

      # For each counter value:
      {

        # Decrement counter and put on third stack
        (((({}<

          # Do mod 2 for end position
          (({}<>)<{({}()<([(){}])>)}{}>)<>

          # Do mod 2 for start position
          (({}(<>))<{({}()<([(){}])>)}{}<>>)

        # Subtract 1 from counter if swap already happened
        ><>({}))(<

          # Compute start position of substrings to consider
          (((({}({})[()])[()()]<>({}))

            # Compute start position of matches to consider
            <>[({})({}){}]({}<>))<>

            # Compute end position of matches to consider
            [(({}<>)<>({}<>)<>)]

          # Push total distance of matches
          )

        # Push counter as base cost of moves
        # Also push additional copy to deal with length 5 intervals starting with an even number
        <>>)))[()](<()>)<

          # With match distance on stack
          <>(({})<

            # Move to location in input data
            ({}{}()){({}()<({}<>)<>>)}{}

            # Make copy of opening bracket to match
            <>(({})<<>(({}<>))>)

          # Mark as first comparison (swap allowed)
          <>(())>)

          # For each bracket to match with:
          {({}[()()]<

            (<([(

              # If swap is allowed in this position:
              {

                # Subtract 1 from cost
                [{}]

                # Add 1 back if swap doesn't perfectly match
                <(({})()<>[({})]<>)>{()(<{}>)}

              }{}

              # Shift copy of first bracket over, while computing differences
              <(({})<>[()({}<(({}<<>({}<>)<>(({})<>)>)<>[(){}])<>>)]<>)>

              # Add 1 if not perfectly matched
              {()(<{}>)}{}

              # Add 1 if neither bracket faces the other
              # Keep 0 on stack to return here
              (){[()](<{}>)}

              # Return to start of brackets
              <<>{({}<>)<>}{}>

            # Add to base cost and place under base cost
            )]({}{}))>)

            # Return to spot in brackets
            # Zero here means swap not allowed for next bracket
            <>{({}<>)<>}

          >)}

          # Cleanup and move everything to right stack
          {}{}<>{}{}{({}<>)<>}{}

          # Remove one copy of base cost, and move list of costs to right stack
          {}(<>)<>{({}<>)<>}{}

          # If swap at end of substring, remove second-last match
          {(<{}>)<>{({}<>)<>}<>({}<{}>){({}<>)<>}}{}

          # Put end of substring on third stack
          ((({}<({}({})({})<

            # If swap at beginning of substring, remove first match
            {{}<>{}(<>)}{}

            # Move start of substring to other stack for safekeeping
            (((({}<({}<>)>)<>)))

          # Create "deletion" record, excluding cost
          <>>)<>>)<>

          # Move data to left stack
          <({}<({}<<>

            # Add cost to deletion record
            (()())

          >)>)>)

          # Put start position on third stack under end position
          <<>({}<

            # For each matching bracket cost:
            {}{

              # Move cost to left stack
              ({}<>)

              # Make three configurations
              ([()()()]){

                # Increment counter
                ((({}()()<>))[()]<

                  # Increment cost in first and third configurations
                  (({()(<{}>)}{})<>({}<

                    # Keep last position constant
                    (({}<

                      # Beginning of second interval: 1, 2, 1 past end of first
                      <>({}[()()]

                        # End of first interval: -3, -1, 1 plus current position
                        (()[({})({})]

                          # Move current position in first and third configurations
                          ({[()](<{}>)}{}<>{}<

                            (({})<>)

                          >)

                        <>)

                      )

                    >)<>)

                  >)<>)

                  # Move data back to left stack
                  <>({}<({}<({}<({}<>)>)>)>)

                >)

              }{}

            {}<>}

            # Eliminate last entry
            # NOTE: This could remove the deletion record if no possible matches.  This is no loss (probably).
            <>{}{}{}{}{}{}{}{}

        # Restore loop variables
        >)>)>)

      }{}

      # With current endpoints on third stack:
      ({}<({}<

        # For all entries
        {

          # Compute locations and move to right stack
          ({}<(({}){({}())}{}{}<(({}){({}())}{}{}<>)>)>)<>

        }

        # For all entries (now on right stack):
        <>{

          # Cost of match
          ((({}

            # Do twice:
            (()()){([{}](

              # Add cost of resulting substrings
              <({}(<()>)<>){({}<({}<>)>(())<>)}{}>({})<<>{{}({}<>)<>}{}>

            # Evaluate as sum of two runs
            ))([{}()]{})}{}

          )))

          # Find smaller of cost and current minimum
          <>(({}))<>{<>({}[()])}{}

          # Push new minimum in place of old minimum
          ({}<<>{}{}{<>}>)

          <>{}

        }

        # Subtract 1 if nonzero
        <>(({}<>){[()](<{}>)}{})(<>)

      >)>)

      <>(<({}<>)>)<>

    # Otherwise (length 3 or less), use 1 from earlier as cost.
    # Note that length 0-1 is impossible here.
    }<>{}

    # With cost on third stack:
    ({}<

      # Find slot number to store cost of interval
      (({}){({}())}{}{})

      # Move to slot
      {({}<({}<>)>(())<>)}{}

    # Store new cost
    {}>)

    # Move other slots back where they should be
    <>{{}({}<>)<>}{}

  Restore length/position for next iteration
  >)<>>)}

  # Clear length/position from inner loop
  {}<>([[]{}])

}{}

(([]){<{}{}>([])}{}<>){({}[()]<{}>)}{}({}<>)

2

Haskell , 797 byte

import Data.Array;import Data.Function;import Data.List;
e=length;f=fst;o=map;s=listArray;u=minimum;b p=let{m=e p;x=s(1,m)p;
v=s(1,m)(listArray('(','}')[0,0..]:[v!i//[(x!i,i)]|i<-[1..m-1]]);
d q=let{n=e q;y=s(1,n)q;t(a,b)=listArray((a,b),(m,n));
c=t(1,1)[sum[1|x!i/=y!j]|i<-[1..m],j<-[1..n]];
d=t(-1,-1)[if i<0||j<0then m+n else 
if i*j<1then(i+j)else u[1+d!(i-1,j),1+d!(i,j-1),c!(i,j)+d!(i-1,j-1),
let{k=v!i!(y!j)-1;l=w!(i,j-1)-1}in-3+i+j-k-l+d!(k,l)]|i<-[-1..m],j<-[-1..n]];
w=t(1,0)[if j>0&&c!(i,j)>0then w!(i,j-1)else j|i<-[1..m],j<-[0..n]]}in d!(m,n);
a=s(0,div m 2)([(m,"")]:[(concat.take 2.groupBy(on(==)f).sort.o(\q->(d q,q)))(
[b:c++[d]|[b,d]<-words"() <> [] {}",(_,c)<-a!(l-1)]++
concat[[b++d,d++b]|k<-[1..div l 2],(_,b)<-a!k,(_,d)<-a!(l-k)])|l<-[1..div m 2]]);
}in u(o(f.head)(elems a))

Hãy thử trực tuyến!


Hôm qua tôi đã đọc ở đây rằng tiền thưởng sẽ không kết thúc trước ngày mai vì vậy tôi muốn tranh luận rằng việc triển khai áp dụng thuật toán en.wikipedia.org/wiki/iêu sẽ tính toán các giá trị chính xác hơn so với heuristic nhanh hơn rất nhiều hiện tại!
Roman Czyborra

Không, điều này hoàn toàn không xứng đáng với giải thưởng vì tôi đã ngoại suy sai rằng việc tìm kiếm 18 ký tự xa 4 trong 2400s @ 800 MHz cũng sẽ làm lu mờ 22 ký tự cách xa 9 gần bằng 3600 mà thật đáng buồn.
Roman Czyborra
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.