Tìm tập độc lập lớn nhất trong đồ thị giống mạng tinh thể cao


16

Đối với một số nguyên dương cho trước n, hãy xem xét tất cả các chuỗi nhị phân có độ dài 2n-1. Đối với một chuỗi cho trước S, chúng ta hãy Llà một mảng có độ dài nchứa đếm số của 1s trong mỗi chuỗi có độ dài ncủa S. Ví dụ, nếu n=3S = 01010sau đó L=[1,2,1]. Chúng tôi gọi Lmảng đếm của S.

Chúng tôi nói rằng hai chuỗi S1S2có cùng độ dài khớp nhau nếu các mảng đếm tương ứng của chúng L1L2có thuộc tính đó L1[i] <= 2*L2[i]L2[i] <= 2*L1[i]cho tất cả i.

Bài tập

Để tăng nbắt đầu từ n=1, nhiệm vụ là tìm kích thước của bộ chuỗi lớn nhất, mỗi độ dài 2n-1sao cho không có hai chuỗi khớp nhau.

Mã của bạn sẽ xuất ra một số cho mỗi giá trị n.

Ghi bàn

Điểm của bạn là cao nhất nmà không ai khác đã đăng câu trả lời đúng cao hơn cho bất kỳ câu trả lời nào của bạn. Rõ ràng nếu bạn có tất cả các câu trả lời tối ưu thì bạn sẽ nhận được điểm cho bài cao nhất nbạn đăng. Tuy nhiên, ngay cả khi câu trả lời của bạn không phải là tối ưu, bạn vẫn có thể đạt được điểm nếu không ai khác có thể đánh bại nó.

Ví dụ câu trả lời

Cho n=1,2,3,4tôi nhận 2,4,10,16.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ và thư viện có sẵn mà bạn thích. Nếu khả thi, sẽ rất tốt để có thể chạy mã của bạn, vì vậy vui lòng bao gồm một lời giải thích đầy đủ về cách chạy / biên dịch mã của bạn trong linux nếu có thể.

Mục hàng đầu

  • 5 của Martin Büttner trong Mathematica
  • 6 bởi Reto Koradi trong C ++ . Giá trị là 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086. 5 đầu tiên được biết là tối ưu.
  • 7 bởi Peter Taylor ở Java . Giá trị là 2, 4, 10, 16, 31, 47, 76, 111, 166, 235.
  • 9 bởi joriki trong Java . Giá trị là 2, 4, 10, 16, 31, 47, 76, 112, 168.

3
Tôi nghĩ sẽ tự nhiên hơn khi hiểu sự bất bình đẳng khi được ký hiệu là L1[i]/2 <= L2[i] <= 2*L1[i].
orlp

1
Ngoài ra, kết hợp không phải là một mối quan hệ tương đương. match(A, B)match(B, C)không ngụ ý match(A, C)(tương tự cho nghịch đảo). Ví dụ: [1] và [2] khớp, [2] và [3] khớp, nhưng [1] và [3] thì không. Tương tự, [1,3] và [3,1] không khớp, [3, 1] và [2, 3] không khớp, nhưng [1, 3] và [2, 3] khớp.
orlp

Câu trả lời:


7

2, 4, 10, 16, 31, 47, 76, 112, 168

Đối với mỗi n, mã Java này xác định các mảng đếm có thể có và sau đó tìm các tập hợp kích thước tăng không khớp, cho mỗi kích thước bắt đầu bằng một tập ngẫu nhiên và cải thiện nó bằng cách hạ xuống dốc ngẫu nhiên. Trong mỗi bước, một trong các phần tử của tập hợp được chọn ngẫu nhiên và được thay thế bằng một mảng đếm khác được chọn ngẫu nhiên trong số các phần tử không được sử dụng. Bước được chấp nhận nếu nó không tăng số lượng trận đấu. Đơn thuốc sau này có vẻ rất quan trọng; chỉ chấp nhận các bước nếu chúng làm giảm số lượng trận đấu gần như không hiệu quả. Các bước để lại số lượng trận đấu bất biến cho phép không gian tìm kiếm được khám phá và cuối cùng một số không gian có thể mở ra để tránh một trong các trận đấu. Sau 2 ^ 24 bước mà không cải thiện, kích thước trước đó là đầu ra cho giá trị hiện tại của n và n được tăng lên.

Các kết quả lên tới n = 9 là 2, 4, 10, 16, 31, 47, 76, 112, 168, cải thiện các kết quả trước đó cho n = 8 bằng một và cho n = 9 bằng hai. Đối với các giá trị cao hơn của n, giới hạn 2 ^ 24 bước có thể phải tăng lên.

Tôi cũng đã thử mô phỏng ủ (với số lượng trận đấu là năng lượng), nhưng không cải thiện được độ dốc cao nhất.

lưu dưới dạng Question54354.java
biên dịch với javac Question54354.java
chạy vớijava Question54354

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class Question54354 {
    static class Array {
        int [] arr;

        public Array (int [] arr) {
            this.arr = arr;
        }

        public int hashCode () {
            return Arrays.hashCode (arr);
        }

        public boolean equals (Object o) {
            return Arrays.equals (((Array) o).arr,arr);
        }
    }

    static int [] indices;
    static int [] [] counts;
    static boolean [] used;

    static Random random = new Random (0);

    static boolean match (int [] c1,int [] c2) {
        for (int k = 0;k < c1.length;k++)
            if (c1 [k] > 2 * c2 [k] || c2 [k] > 2 * c1 [k])
                return false;
        return true;
    }

    static int matches (int i) {
        int result = 0;
        for (int j = 0;j < indices.length;j++)
            if (j != i && match (counts [indices [i]],counts [indices [j]]))
                result++;
        return result;
    }

    static void randomize (int i) {
        do
            indices [i] = random.nextInt (counts.length);
        while (used [indices [i]]);
    }

    public static void main (String [] args) {
        for (int n = 1,length = 1;;n++,length += 2) {
            int [] lookup = new int [1 << n];
            for (int string = 0;string < 1 << n;string++)
                for (int bit = 1;bit < 1 << n;bit <<= 1)
                    if ((string & bit) != 0)
                        lookup [string]++;
            Set<Array> arrays = new HashSet<Array> ();
            for (int string = 0;string < 1 << length;string++) {
                int [] count = new int [n];
                for (int i = 0;i < n;i++)
                    count [i] = lookup [(string >> i) & ((1 << n) - 1)];
                arrays.add (new Array (count));
            }
            counts = new int [arrays.size ()] [];
            int j = 0;
            for (Array array : arrays)
                counts [j++] = array.arr;
            used = new boolean [counts.length];

            int m;
            outer:
            for (m = 1;m <= counts.length;m++) {
                indices = new int [m];
                for (;;) {
                    Arrays.fill (used,false);
                    for (int i = 0;i < m;i++) {
                        randomize (i);
                        used [indices [i]] = true;
                    }
                    int matches = 0;
                    for (int i = 0;i < m;i++)
                        matches += matches (i);
                    matches /= 2;
                    int stagnation = 0;
                    while (matches != 0) {
                        int k = random.nextInt (m);
                        int oldMatches = matches (k);
                        int oldIndex = indices [k];
                        randomize (k);
                        int newMatches = matches (k);
                        if (newMatches <= oldMatches) {
                            if (newMatches < oldMatches) {
                                matches += newMatches - oldMatches;
                                stagnation = 0;
                            }
                            used [oldIndex] = false;
                            used [indices [k]] = true;
                        }
                        else
                            indices [k] = oldIndex;

                        if (++stagnation == 0x1000000)
                            break outer;
                    }
                    break;
                }
            }
            System.out.println (n + " : " + (m - 1));
        }
    }
}

1
Một cải tiến rất tốt đẹp!

11

2, 4, 10, 16, 31, 47, 76, 111, 166, 235

Ghi chú

Nếu chúng ta xem xét một đồ thị Gcó các đỉnh 0tới nvà các cạnh nối hai số khớp nhau, thì công suất tenor G^n có các đỉnh (x_0, ..., x_{n-1})tạo thành công suất của Cartesian {0, ..., n}^nvà các cạnh giữa các bộ dữ liệu khớp. Biểu đồ quan tâm là biểu đồ con được G^n tạo bởi các đỉnh tương ứng với "mảng đếm" có thể.

Vì vậy, nhiệm vụ đầu tiên là tạo ra các đỉnh đó. Cách tiếp cận ngây thơ liệt kê qua các 2^{2n-1}chuỗi, hoặc theo thứ tự 4^n. Nhưng nếu chúng ta thay vào đó nhìn vào mảng các khác biệt đầu tiên của mảng đếm, chúng ta thấy rằng chỉ có các 3^nkhả năng và từ những khác biệt đầu tiên, chúng ta có thể suy ra phạm vi của các giá trị ban đầu có thể bằng cách yêu cầu không có phần tử nào trong các khác biệt của zeroth nhỏ hơn 0hoặc lớn hơn n.

Sau đó chúng tôi muốn tìm tập độc lập tối đa. Tôi đang sử dụng một định lý và hai heuristic:

  • Định lý: tập hợp độc lập tối đa của một tập hợp đồ thị rời rạc là tập hợp các tập độc lập tối đa của chúng. Vì vậy, nếu chúng ta chia một biểu đồ thành các thành phần không được kết nối, chúng ta có thể đơn giản hóa vấn đề.
  • Heuristic: giả định rằng (n, n, ..., n)sẽ ở trong một tập độc lập tối đa. Có một đỉnh khá lớn trong {m, m+1, ..., n}^nđó mcác số nguyên nhỏ nhất khớp với nhau n; (n, n, ..., n)được đảm bảo không có trận đấu bên ngoài cụm đó.
  • Heuristic: thực hiện phương pháp tham lam chọn đỉnh của mức độ thấp nhất.

Trên máy tính của tôi phát hiện này 111cho n=8trong 16 giây, 166cho n=9trong khoảng 8 phút, và 235cho n=10vào khoảng 2 giờ.

Lưu dưới dạng PPCG54354.java, biên dịch thành javac PPCG54354.javavà chạy như java PPCG54354.

import java.util.*;

public class PPCG54354 {
    public static void main(String[] args) {
        for (int n = 1; n < 20; n++) {
            long start = System.nanoTime();

            Set<Vertex> constructive = new HashSet<Vertex>();
            for (int i = 0; i < (int)Math.pow(3, n-1); i++) {
                int min = 0, max = 1, diffs[] = new int[n-1];
                for (int j = i, k = 0; k < n-1; j /= 3, k++) {
                    int delta = (j % 3) - 1;
                    if (delta == -1) min++;
                    if (delta != 1) max++;
                    diffs[k] = delta;
                }

                for (; min <= max; min++) constructive.add(new Vertex(min, diffs));
            }

            // Heuristic: favour (n, n, ..., n)
            Vertex max = new Vertex(n, new int[n-1]);
            Iterator<Vertex> it = constructive.iterator();
            while (it.hasNext()) {
                Vertex v = it.next();
                if (v.matches(max) && !v.equals(max)) it.remove();
            }

            Set<Vertex> ind = independentSet(constructive, n);
            System.out.println(ind.size() + " after " + ((System.nanoTime() - start) / 1000000000L) + " secs");
        }
    }

    private static Set<Vertex> independentSet(Set<Vertex> vertices, int dim) {
        if (vertices.size() < 2) return vertices;

        for (int idx = 0; idx < dim; idx++) {
            Set<Set<Vertex>> p = connectedComponents(vertices, idx);
            if (p.size() > 1) {
                Set<Vertex> ind = new HashSet<Vertex>();
                for (Set<Vertex> part : connectedComponents(vertices, idx)) {
                    ind.addAll(independentSet(part, dim));
                }
                return ind;
            }
        }

        // Greedy
        int minMatches = Integer.MAX_VALUE;
        Vertex minV = null;
        for (Vertex v0 : vertices) {
            int numMatches = 0;
            for (Vertex vi : vertices) if (v0.matches(vi)) numMatches++;
            if (numMatches < minMatches) {
                minMatches = numMatches;
                minV = v0;
            }
        }

        Set<Vertex> nonmatch = new HashSet<Vertex>();
        for (Vertex vi : vertices) if (!minV.matches(vi)) nonmatch.add(vi);
        Set<Vertex> ind = independentSet(nonmatch, dim);
        ind.add(minV);
        return ind;
    }

    // Separates out a set of vertices which form connected components when projected into the idx axis.
    private static Set<Set<Vertex>> connectedComponents(Set<Vertex> vertices, final int idx) {
        List<Vertex> sorted = new ArrayList<Vertex>(vertices);
        Collections.sort(sorted, new Comparator<Vertex>() {
                public int compare(Vertex a, Vertex b) {
                    return a.x[idx] - b.x[idx];
                }
            });

        Set<Set<Vertex>> connectedComponents = new HashSet<Set<Vertex>>();
        Set<Vertex> current = new HashSet<Vertex>();
        int currentVal = 0;
        for (Vertex v : sorted) {
            if (!match(currentVal, v.x[idx]) && !current.isEmpty()) {
                connectedComponents.add(current);
                current = new HashSet<Vertex>();
            }

            current.add(v);
            currentVal = v.x[idx];
        }

        if (!current.isEmpty()) connectedComponents.add(current);
        return connectedComponents;
    }

    private static boolean match(int a, int b) {
        return a <= 2 * b && b <= 2 * a;
    }

    private static class Vertex {
        final int[] x;
        private final int h;

        Vertex(int[] x) {
            this.x = x.clone();

            int _h = 0;
            for (int xi : x) _h = _h * 37 + xi;
            h = _h;
        }

        Vertex(int x0, int[] diffs) {
            x = new int[diffs.length + 1];
            x[0] = x0;
            for (int i = 0; i < diffs.length; i++) x[i+1] = x[i] + diffs[i];

            int _h = 0;
            for (int xi : x) _h = _h * 37 + xi;
            h = _h;
        }

        public boolean matches(Vertex v) {
            if (v == this) return true;
            if (x.length != v.x.length) throw new IllegalArgumentException("v");
            for (int i = 0; i < x.length; i++) {
                if (!match(x[i], v.x[i])) return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            return h;
        }

        @Override
        public boolean equals(Object obj) {
            return (obj instanceof Vertex) && equals((Vertex)obj);
        }

        public boolean equals(Vertex v) {
            if (v == this) return true;
            if (x.length != v.x.length) return false;
            for (int i = 0; i < x.length; i++) {
                if (x[i] != v.x[i]) return false;
            }
            return true;
        }

        @Override
        public String toString() {
            if (x.length == 0) return "e";

            StringBuilder sb = new StringBuilder(x.length);
            for (int xi : x) sb.append(xi < 10 ? (char)('0' + xi) : (char)('A' + xi - 10));
            return sb.toString();
        }
    }
}

10

Toán học n = 5, 31 chuỗi

Tôi vừa viết một giải pháp vũ phu sử dụng các phần dựng sẵn của Mathematica để xác minh các câu trả lời mẫu của Lembik, nhưng nó cũng có thể xử lý n = 5:

n = 5;
s = Tuples[{0, 1}, 2 n - 1];
l = Total /@ Partition[#, n, 1] & /@ s
g = Graph[l, 
  Cases[Join @@ Outer[UndirectedEdge, l, l, 1], 
   a_ <-> b_ /; 
    a != b && And @@ Thread[a <= 2 b] && And @@ Thread[b <= 2 a]]]
set = First@FindIndependentVertexSet[g]
Length@set

Như một phần thưởng, mã này tạo ra một hình ảnh trực quan của vấn đề dưới dạng biểu đồ trong đó mỗi cạnh chỉ ra hai chuỗi khớp.

Đây là biểu đồ cho n = 3:

nhập mô tả hình ảnh ở đây


2
Lúc đầu, tôi nghĩ rằng biểu đồ là đối xứng độc đáo, nhưng sau đó tôi thấy điểm bù nhẹ ở bên trái. Không thể bỏ qua :(
orlp

3

C ++ (heuristic): 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086

Đây là một chút đằng sau kết quả Peter Taylor, là 1-3 thấp hơn cho n=7, 910. Ưu điểm là nó nhanh hơn nhiều, vì vậy tôi có thể chạy nó với giá trị cao hơn n. Và nó có thể được hiểu mà không cần bất kỳ toán học ưa thích. ;)

Mã hiện tại được kích thước để chạy đến n=15. Thời gian chạy tăng khoảng 4 lần cho mỗi lần tăng n. Ví dụ: thời gian là 0,008 giây n=7, 0,07 giây n=9, 1,34 giây n=11, 27 giây n=13và 9 phút n=15.

Có hai quan sát chính tôi đã sử dụng:

  • Thay vì tự vận hành các giá trị, heuristic hoạt động dựa trên việc đếm mảng. Để làm điều này, một danh sách tất cả các mảng đếm duy nhất được tạo ra đầu tiên.
  • Sử dụng các mảng đếm với các giá trị nhỏ sẽ có lợi hơn, vì chúng loại bỏ ít không gian giải pháp hơn. Này được dựa trên mỗi đếm ctrừ phạm vi c / 2đến 2 * ctừ các giá trị khác. Đối với các giá trị nhỏ hơn c, phạm vi này nhỏ hơn, có nghĩa là loại trừ ít giá trị hơn bằng cách sử dụng nó.

Tạo Mảng đếm độc đáo

Tôi đã dùng vũ lực cho cái này, lặp qua tất cả các giá trị, tạo ra mảng đếm cho mỗi trong số chúng và duy nhất danh sách kết quả. Điều này chắc chắn có thể được thực hiện hiệu quả hơn, nhưng nó đủ tốt cho các loại giá trị chúng tôi đang hoạt động.

Điều này là cực kỳ nhanh chóng cho các giá trị nhỏ. Đối với các giá trị lớn hơn, chi phí không đáng kể. Ví dụ, đối với n=15, nó sử dụng khoảng 75% toàn bộ thời gian chạy. Đây chắc chắn sẽ là một khu vực để xem xét khi cố gắng đi cao hơn nhiều n=15. Ngay cả việc sử dụng bộ nhớ để xây dựng danh sách các mảng đếm cho tất cả các giá trị cũng sẽ bắt đầu trở nên có vấn đề.

Số lượng mảng đếm duy nhất là khoảng 6% số lượng giá trị cho n=15. Số lượng tương đối này trở nên nhỏ hơn khi ntrở nên lớn hơn.

Tham lam lựa chọn các giá trị mảng đếm

Phần chính của thuật toán chọn đếm các giá trị mảng từ danh sách được tạo bằng cách sử dụng một cách tiếp cận tham lam đơn giản.

Dựa trên lý thuyết rằng sử dụng mảng đếm với số lượng nhỏ là có lợi, mảng đếm được sắp xếp theo tổng số đếm của chúng.

Sau đó, chúng được kiểm tra theo thứ tự và một giá trị được chọn nếu nó tương thích với tất cả các giá trị được sử dụng trước đó. Vì vậy, điều này liên quan đến một lần truyền tuyến tính duy nhất thông qua các mảng đếm duy nhất, trong đó mỗi ứng cử viên được so sánh với các giá trị đã được chọn trước đó.

Tôi có một số ý tưởng về cách heuristic có thể được cải thiện. Nhưng điều này có vẻ như là một điểm khởi đầu hợp lý, và kết quả có vẻ khá tốt.

Điều này không được tối ưu hóa cao. Tôi đã có một cấu trúc dữ liệu phức tạp hơn tại một số điểm, nhưng nó sẽ cần nhiều công việc hơn để khái quát hóa nó hơn nữa n=8, và sự khác biệt về hiệu suất dường như không đáng kể.

#include <cstdint>
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <sstream>
#include <iostream>

typedef uint32_t Value;

class Counter {
public:
    static void setN(int n);

    Counter();
    Counter(Value val);

    bool operator==(const Counter& rhs) const;
    bool operator<(const Counter& rhs) const;

    bool collides(const Counter& other) const;

private:
    static const int FIELD_BITS = 4;
    static const uint64_t FIELD_MASK = 0x0f;

    static int m_n;
    static Value m_valMask;

    uint64_t fieldSum() const;

    uint64_t m_fields;
};

void Counter::setN(int n) {
    m_n = n;
    m_valMask = (static_cast<Value>(1) << n) - 1;
}

Counter::Counter()
  : m_fields(0) {
}

Counter::Counter(Value val) {
    m_fields = 0;
    for (int k = 0; k < m_n; ++k) {
        m_fields <<= FIELD_BITS;
        m_fields |= __builtin_popcount(val & m_valMask);
        val >>= 1;
    }
}

bool Counter::operator==(const Counter& rhs) const {
    return m_fields == rhs.m_fields;
}

bool Counter::operator<(const Counter& rhs) const {
    uint64_t lhsSum = fieldSum();
    uint64_t rhsSum = rhs.fieldSum();
    if (lhsSum < rhsSum) {
        return true;
    }
    if (lhsSum > rhsSum) {
        return false;
    }

    return m_fields < rhs.m_fields;
}

bool Counter::collides(const Counter& other) const {
    uint64_t fields1 = m_fields;
    uint64_t fields2 = other.m_fields;

    for (int k = 0; k < m_n; ++k) {
        uint64_t c1 = fields1 & FIELD_MASK;
        uint64_t c2 = fields2 & FIELD_MASK;

        if (c1 > 2 * c2 || c2 > 2 * c1) {
            return false;
        }

        fields1 >>= FIELD_BITS;
        fields2 >>= FIELD_BITS;
    }

    return true;
}

int Counter::m_n = 0;
Value Counter::m_valMask = 0;

uint64_t Counter::fieldSum() const {
    uint64_t fields = m_fields;
    uint64_t sum = 0;
    for (int k = 0; k < m_n; ++k) {
        sum += fields & FIELD_MASK;
        fields >>= FIELD_BITS;
    }

    return sum;
}

typedef std::vector<Counter> Counters;

int main(int argc, char* argv[]) {
    int n = 0;
    std::istringstream strm(argv[1]);
    strm >> n;

    Counter::setN(n);

    int nBit = 2 * n - 1;
    Value maxVal = static_cast<Value>(1) << nBit;

    Counters allCounters;

    for (Value val = 0; val < maxVal; ++val) {
        Counter counter(val);
        allCounters.push_back(counter);
    }

    std::sort(allCounters.begin(), allCounters.end());

    Counters::iterator uniqEnd =
        std::unique(allCounters.begin(), allCounters.end());
    allCounters.resize(std::distance(allCounters.begin(), uniqEnd));

    Counters solCounters;
    int nSol = 0;

    for (Value idx = 0; idx < allCounters.size(); ++idx) {
        const Counter& counter = allCounters[idx];

        bool valid = true;
        for (int iSol = 0; iSol < nSol; ++iSol) {
            if (solCounters[iSol].collides(counter)) {
                valid = false;
                break;
            }
        }

        if (valid) {
            solCounters.push_back(counter);
            ++nSol;
        }
    }

    std::cout << "result: " << nSol << std::endl;

    return 0;
}

Tôi đã có các giải pháp đệ quy dựa trên mã tương tự được đảm bảo để tìm mức tối đa. Nhưng phải mất một lúc n=4rồi. Nó có thể đã hoàn thành n=5với một chút kiên nhẫn. Bạn phải sử dụng một chiến lược quay lui tốt hơn để thậm chí thực hiện nó n=7. Là heuristic của bạn, hoặc nó đã khám phá toàn bộ không gian giải pháp? Tôi đang suy nghĩ về một số ý tưởng về cách làm cho điều này tốt hơn, bằng cách điều chỉnh thứ tự sắp xếp, hoặc có thể không hoàn toàn tham lam.
Reto Koradi

Sự hiểu biết của tôi là không có sự quay lại trong câu trả lời của Peter Taylor. Nó hoàn toàn tham lam. Thủ thuật chính là anh ta giảm số lượng mảng đếm 3^nvà hai phương pháp phỏng đoán mà anh ta mô tả.

@Lembik Nhận xét của tôi là phản hồi lại một bình luận đã bị xóa. Số lượng mảng đếm phải giống nhau, vì tôi xây dựng dựa trên các giá trị thực tế và giảm nó xuống chỉ còn các mảng duy nhất. Tôi đã cập nhật một phiên bản quay lui của thuật toán. Mặc dù nó không chấm dứt trong một thời gian hợp lý, nhưng nó tìm thấy 76 n=7một cách nhanh chóng. Nhưng thử nó n=9, nó vẫn bị kẹt ở 164 khi tôi dừng nó sau 20 phút. Vì vậy, việc mở rộng điều này với một hình thức quay lui đơn giản hạn chế nhìn chung không có triển vọng.
Reto Koradi
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.