Có thể tìm thấy nếu một chuỗi tồn tại trong thời gian đa thức trong vấn đề sau đây?


27

Tôi đã suy nghĩ về vấn đề sau đây một thời gian và tôi đã không tìm thấy một giải pháp đa thức cho nó. Chỉ có nguồn gốc vũ phu. Tôi đã cố gắng giảm một vấn đề NP-Complete vào nó mà không thành công.

Đây là vấn đề :


Bạn có một tập hợp được sắp xếp {(A1,B1),(A2,B2),,(An,Bn)} của các cặp số nguyên dương.

(Ai,Bi)<(Aj,Bj)Ai<Aj(Ai=AjBi<Bj) (Ai,Bi)=(Aj,Bj)Ai=AjBi=Bj

Các hoạt động sau đây có thể được áp dụng cho một cặp : Swap(pair). Nó hoán đổi các yếu tố của cặp, vì vậy (10,50) sẽ trở thành (50,10)

Khi một cặp trong bộ được hoán đổi, bộ sẽ tự động được sắp xếp lại (cặp được hoán đổi nằm ngoài vị trí và nó sẽ được di chuyển vào vị trí của nó trong bộ).

Vấn đề bao gồm xem có một chuỗi, bắt đầu từ một số cặp, hoán đổi toàn bộ tập hợp, với điều kiện sau:

Sau khi một cặp được hoán đổi, cặp tiếp theo được hoán đổi phải là cặp kế hoặc cặp tiền thừa trong tập hợp.


Sẽ thật tuyệt vời khi tìm một giải pháp thời gian đa thức cho vấn đề này, hoặc giảm một vấn đề NP-Complete vào nó.

Lưu ý:
Đây đã là một vấn đề quyết định. Tôi không muốn biết đó là chuỗi nào: chỉ khi một chuỗi tồn tại.

Ví dụ về cách tập hợp được sắp xếp sau khi hoán đổi một cặp

(6, 5)
(1,2)
(3,4)
(7,8)

Nếu tôi hoán đổi cặp đầu tiên, nó sẽ trở thành: (5,6) và sau khi sắp xếp bộ (đặt cặp đã sắp xếp vào vị trí mới của nó), chúng ta có:

(1,2)
(3,4)
(5,6)
(7,8)

Sau đó, tôi phải hoán đổi cặp (tiền thân) hoặc ( 7 , 8 ) (thành công) và lặp lại quy trình cho đến khi tất cả các cặp được hoán đổi (nếu có thể).(3,4)(7,8)

Quan trọng:
Bạn không thể trao đổi một cặp đã hoán đổi.
Nếu có một chuỗi các hoạt động 'trao đổi', thì tất cả các cặp phải được đổi tên thành một lần và chỉ một lần.

Ví dụ khi không thể hoán đổi tất cả các cặp

( 1 , 4 ) ( 3 , 2 ) ( 5 , 5 )(0,0)
(1,4)
(3,2)
(5,5)


1
Danh sách có được sắp xếp sau khi bạn đổi tên tệp và trước khi bạn chọn tệp tiếp theo để đổi tên không? Bạn có thể viết lại điều kiện sắp xếp như sau: khi và chỉ khi ( A < A ' ) hoặc ( A = A 'B < B ' ) hoặc ( A = A 'B = B 'C < C '(A,B,C)<(A,B,C)A<AA=AB<BA=AB=BC<C)?
mjqxxxx

3
Các vấn đề chuyển nhượng không được chào đón trên cstheory.stackexchange.com nói chung.
Tsuyoshi Ito

3
Hmm, tôi không chắc chắn. Thông thường logic ở đây là không phải là một cách thực hành tốt để trả lời các câu hỏi bài tập về nhà điển hình bởi vì làm như vậy sẽ phá hỏng mục đích làm bài tập về nhà cho ai đó trong tương lai. Nhưng trong trường hợp này, vấn đề không giống như một vấn đề điển hình .
Tsuyoshi Ito

2
có lẽ nếu bạn đưa ra một động lực khác với "đó là bài tập về nhà", mọi người có thể quan tâm và nó sẽ không bị đóng cửa. Điều gì có thể là một ứng dụng có thể của điều này?
Marcos Villagra

2
về việc định hình lại vấn đề, bạn có thể quên các tập tin và xem nó theo cách này. Bạn có một tập hợp các cặp số nguyên dương và các quy tắc giống như bạn đặt nó. Ban đầu được sắp xếp trong cột đầu tiên, sau đó bạn bắt đầu đổi tên các điểm. A={(x1,y1),,(xn,yn)}
Marcos Villagra

Câu trả lời:


16

... Tôi đã tìm kiếm một số mẫu để tạo ra sự giảm bớt từ một vấn đề NPC, nhưng không tìm ra cách nào để thể hiện một "dòng chảy" bằng một "ngã ba" ...

Vì vậy (sau một số công việc) đây là một thuật toán đa thức ...

TIẾNG VIỆT

Danh sách bắt đầu có thể được xem như là một mảng của "liên tiếp lỗ ". Đối với mỗi cặp ban đầu ( a j , b j ) , đặt " phần tử " b j vào lỗ số a j . Mỗi cặp có thể được xem như một cạnh được định hướng từ vị trí a j đến vị trí b j . Một di chuyển bao gồm trong việc lựa chọn một yếu tố b j ở vị trí một j và di chuyển nó đến vị trí của nó đến b jN2(aj,bj)bjajajbjbjajbj(lỗ đích trở thành một chốt không thể di chuyển ). Chúng tôi xóa cạnh và tiến hành chọn bước tiếp theo sẽ bắt đầu từ một trong hai yếu tố có thể tiếp cận gần nhất từ vị trí b j (chỉ cho phép các lỗ giữa b jb k ). Chúng ta phải tìm một chuỗi N di chuyển liên tiếp.bkbjbjbkN

  • Với mỗi coi b j (tại vị trí mảng a j ) là phần tử bắt đầu s t a r t .(aj,bj)bjajstart

    • Đối với mỗi xem xét một k là yếu tố cuối cùng e n d (mép từ vị trí một k đến vị trí b k sẽ là lợi thế cạnh cuối cùng).(ak,bk),akajakendakbk

      • tạo một chuỗi các bước di chuyển từ bằng cách sử dụng các tiêu chí sau cho đến khi bạn đạt được yếu tố e n d (và đã tìm thấy giải pháp) hoặc điều kiện dừngstartend

Khi bạn thực hiện di chuyển, bạn sửa một chốt ở vị trí và mảng được chia thành hai phân vùng L (trái) và R (phải) và cách duy nhất để đi từ L đến R (hoặc từ R đến L ) là sử dụng một cạnh đó nhảy qua chốt. BộbjLRLRRL

  • = số cạnh từ trái sang phải (không tính cạnh cuối cùng)edgesLR
  • = số cạnh từ phải sang trái (không tính cạnh cuối cùng)edgesRL
  • = e d g e s L R - e d g e s R LflowedgesLRedgesRL

Các trường hợp:

A) nếu thì một trong hai phân vùng sẽ không thể truy cập được, dừng lại|flow|>1

Bây giờ giả sử rằng , tức là e n d Rend>bjendR

B) nếu thì có thêm một cạnh từ trái sang phải, bạn phải đi sang trái (chọn phần tử gần nhất của L ), nếu không bạn sẽ không bao giờ đạt được e n dflow=1Lend

C) nếu thì có thêm một cạnh từ phải sang trái và bất kỳ nút nào bạn chọn, bạn sẽ không bao giờ đạt được e n d , dừng lạiflow=1end

D) nếu bạn phải đi đúng (chọn phần tử gần nhất của R ), nếu không bạn sẽ đạt được e n dflow=0Rend

Nếu ( e n d L ), B, C, D bị đảo ngược.end<bjendL

LƯU Ý: khi di chuyển sang trái hoặc phải, bạn phải coi là một chốt. Ví dụ: nếu bạn phải đi đúng, nhưng phần tử gần nhất trên Re n d thì việc di chuyển là không thể (và bạn phải tiến hành với một cặp khác ( s t a r t , e n d ) )endRend(start,end)

Áp dụng cùng một quy hoạch lại ở mỗi di chuyển.

THÀNH PHẦN

Dòng chảy qua mỗi lỗ có thể được xác định trước trong O (N) và được sử dụng lại ở mỗi lần quét.

Các vòng lặp là:

for start = 1 to N
  for end = 1 to N
    for move = 1 to N
      make a move (fix a peg and update flows)
      check if another move can be done using flow     

Không có lựa chọn nào được thực hiện trong quá trình tính toán, vì vậy độ phức tạp của thuật toán là O(N3)

Đây là một triển khai Java hoạt động của thuật toán:

public class StrangeSort {
    static int PEG = 0xffffff, HOLE = 0x0;
    static int M = 0, N = 0, choices = 0, aux = 0, end;
    static int problem[][], moves[], edgeflow[], field[];    
    boolean is_hole(int x) { return x == HOLE; }
    boolean is_peg(int x) { return x == PEG; }
    boolean is_ele(int x) { return ! is_peg(x) && ! is_hole(x); };
    int []cp(int src[]) { // copy an array
        int res[] = new int[src.length];
        System.arraycopy(src, 0, res, 0, res.length);
        return res;
    }    
    /* find the first element on the left (dir=-1) right (dir=1) */
    int find(int pos, int dir, int nm) {
        pos += dir;
        while (pos >= 1 && pos <= M ) {
            int x = field[pos];
            if ( is_peg(x) || (pos == end && nm < N-1) ) return 0;
            if ( is_ele(x) ) return pos;
            pos += dir;
        }
        return 0;
    }
    void build_edges() {
        edgeflow = new int[M+1];
        for (int i = 1; i<=M; i++) {
            int start = i;
            int b = field[start];
            if (! is_ele(b)) continue;
            if (i == end) continue;
            int dir = (b > start)? 1 : -1;
            start += dir;
            while (start != b) { edgeflow[start] += dir; start += dir; }
        }
    }
    boolean rec_solve(int start, int nm) {
        boolean f;
        int j;
        int b = field[start];
        moves[nm++] = b;
        if (nm == N) return true;
        //System.out.println("Processing: " + start + "->" + field[start]);        
        field[start] = HOLE;
        field[b] = PEG;
        int dir = (b > start)? 1 : -1;
        int i = start + dir;
        while (i != b) { edgeflow[i] -= dir; i += dir; } // clear edge                
        int flow = edgeflow[b];
        if (Math.abs(flow) > 2) return false;
        if (end > b) {
            switch (flow) {
            case 1 :                    
                j = find(b,-1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            case -1 :
                return false;
            case 0 :          
                j = find(b,1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            }        
        } else {
            switch (flow) {
            case -1 :                    
                j = find(b,1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            case 1 :
                return false;
            case 0 :          
                j = find(b,-1,nm);
                if (j <= 0) return false;
                return rec_solve(j,nm);
            }            
        }
        return false;
    }
    boolean solve(int demo[][]) {
        N = demo.length;
        for (int i = 0; i < N; i++)
            M = Math.max(M, Math.max(demo[i][0], demo[i][1]));
        moves = new int[N];
        edgeflow = new int[M+1];
        field = new int[M+1];
        problem = demo;        
        for (int i = 0; i < problem.length; i++) {
            int a = problem[i][0];
            int b = problem[i][1];
            if ( a < 1 || b < 1 || a > M || b > M || ! is_hole(field[a]) || ! is_hole(field[b])) {
                System.out.println("Bad input pair (" + a + "," + b + ")");
                return false;
            }
            field[a] = b;
        }
        for (int i = 1; i <= M; i++) {
            end = i;
            build_edges();
            if (!is_ele(field[i])) continue;
            for (int j = 1; j <= M; j++) {
                if (!is_ele(field[j])) continue;
                if (i==j) continue;
                int tmp_edgeflow[] = cp(edgeflow);
                int tmp_field[] = cp(field);
                choices = 0;
                //System.out.println("START: " + j + " " + " END: " + i);
                if (rec_solve(j, 0)) {
                    return true;
                }
                edgeflow = tmp_edgeflow;
                field = tmp_field;
            }
        }
        return false;
    }
    void init(int demo[][]) {

    }
    public static void main(String args[]) {
        /**** THE INPUT ********/        

        int demo[][] =  {{4,2},{5,7},{6,3},{10,12},{11,1},{13,8},{14,9}};

        /***********************/        
        String r = "";
        StrangeSort sorter = new StrangeSort();       
        if (sorter.solve(demo)) {
            for (int i = 0; i < N; i++) { // print it in clear text
                int b =  moves[i];
                for (int j = 0; j < demo.length; j++)
                    if (demo[j][1] == b)
                        r += ((i>0)? " -> " : "") + "(" + demo[j][0] + "," + demo[j][1] + ")";
            }             
            r = "SOLUTION: "+r;
        }
        else
            r = "NO SOLUTIONS";
        System.out.println(r);
    }    
}

Đây là một cách tiếp cận thú vị. Nói chung, bất cứ khi nào bạn sử dụng một cạnh , phải có số lượng bằng nhau (hoặc khác nhau bởi một) số cạnh không được sử dụng cắt ngang b theo mỗi hướng; và nếu các số khác nhau một, bạn sẽ biết bạn phải chọn cạnh nào tiếp theo. Khi các số bằng nhau, bạn có một lựa chọn, bạn phải giải quyết bằng cách kiểm tra cả hai tùy chọn. Nó có vẻ như là một chiến lược tìm kiếm đủ hiệu quả, nhưng làm thế nào để bạn biết nó là đa thức trong trường hợp xấu nhất? Tức là, làm thế nào để bạn biết bạn sẽ chỉ gặp phải các lựa chọn O ( log n ) trong đó số cạnh không được sử dụng theo mỗi hướng là bằng nhau? (a,b)bO(logn)
mjqxxxx

@mjqxxxx ... Tôi viết lại toàn bộ câu trả lời để khớp với thuật toán Java ...
Marzio De Biasi

@mjqxxxx ... ok, cuối cùng tôi đã nhận được ... :-)
Marzio De Biasi

2
Điều này có vẻ đúng và rất thanh lịch với tôi. Khi bạn sử dụng một cạnh , bạn không còn có thể "đi bộ" qua b ; các chuyển tiếp duy nhất còn lại trên b là các "bước nhảy" không được sử dụng (các cạnh được định hướng) đi qua nó, tất cả đều phải sử dụng. Nếu cạnh cuối cùng ( a n , b n ) được chỉ định, thì bạn cần cuộn dây ở cùng phía của b với a n(a,b)bb(an,bn)ban. Sau đó, chỉ có một hướng duy nhất có thể đi bộ sau mỗi cạnh, vì một số lần nhảy lẻ (chẵn) sẽ khiến bạn ở phía đối diện (giống nhau) ban đầu bạn bước tới. Vì vậy, kiểm tra mỗi lựa chọn các cạnh bắt đầu và kết thúc có thể được thực hiện trong thời gian đa thức.
mjqxxxx

1
Đây là một thuật toán đẹp. Nó chưa bao giờ xảy ra với tôi để sửa bước cuối cùng trước. Điểm nhỏ: (1) Như mjqxxxx đã viết, kết thúc phải là a_k. Nếu không thì điều kiện kết thúc và b> j sai. (2) Hoặc là định nghĩa của dòng chảy Dòng phải được phủ định, hoặc các trường hợp B và C phải được hoán đổi.
Tsuyoshi Ito

10

Đây không phải là một giải pháp, mà là một cuộc cải cách tránh đề cập rõ ràng về các hoạt động hoán đổi và sắp xếp. Bắt đầu bằng cách sắp xếp toàn bộ danh sách tên tệp kết hợp và các phiên bản hoán đổi của chúng và xác định từng tên tệp với chỉ mục của nó trong danh sách đó. Sau đó, hai tệp là hàng xóm nếu tất cả tên tệp cũ giữa chúng đã bị hủy và nếu không có tên tệp mới nào giữa chúng được tạo. Vấn đề được cải cách là như sau:

Cho một tập của rời nhau đạo cạnh ( một , b ) với một , b { 1 , 2 , ... , 2 n } , là có một trật tự ( một 1 , b 1 ) , ( một 2 , b 2 ) ,n(a,b)a,b{1,2,,2n} của các cạnh này sao cho(a1,b1),(a2,b2),...,(an,bn)

  • nếu nằm giữa b iajbi , sau đó j i , vàai+1ji
  • nếu nằm giữa b ia i + 1bjbiai+1 , sau đó ?ji+1

2
+1. Đây là một cách đơn giản hơn nhiều để nêu vấn đề tương đương. Chỉ cần làm rõ một điều: các cạnh (a, b) được định hướng (theo nghĩa là cạnh (a, b) và cạnh (b, a) có ý nghĩa khác nhau).
Tsuyoshi Ito

@Tsuyoshi: cảm ơn; Tôi chỉnh sửa để nói 'đạo'.
mjqxxxx

bacabc

@Oleksandr: Ở đây, B b nằm giữa a và c Có nghĩa là Cam hoặc là một <b <c hoặc c <b <a
.iết
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.