在以下问题中是否可以找到多项式时间内是否存在序列?


27

我一直在思考以下问题,但尚未找到多项式解决方案。只有蛮力。我一直在尝试将NP完全问题简化为没有成功的问题。

这是问题所在


您有一个正整数对的排序集。 {(A1,B1),(A2,B2),,(An,Bn)}

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

以下操作可以应用于一对:Swap(pair)。它交换该对中的元素,因此将变为(10,50)(50,10)

交换集合中的一对时,该集合将自动再次排序(交换后的对不适当,它将移入集合中的位置)。

问题在于查看是否有从某些对开始的序列交换整个集合,并具有以下条件:

一对交换后,下一个要交换的对必须是集合中的后继对或前导对。


找到这个问题的多项式时间解,或者将NP-完全问题简化为一个很好的解决方案。

注意:
这已经是一个决策问题。我不想知道序列是什么:仅当序列存在时。

交换对后如何对集合进行排序的示例

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

如果我交换第一对,它将变成:,然后对集合进行排序(将已排序的对放置在新位置),我们将:(5,6)

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

然后,我必须交换(前任)对或(后继)对,并重复该过程,直到所有对都被交换为止(如果可能)。7 8 (3,4)(7,8)

重要:
您不能交换已交换的对。
如果有一系列“交换”操作,则所有对必须重命名一次,并且只能重命名一次。

无法交换所有货币对的示例

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


1
在重命名文件之后,选择下一个要重命名的文件之前,列表是否已排序?您可以将排序条件重写为: iff()或(和)或(和和)?< '= '< '= '= ' ç < Ç '(A,B,C)<(A,B,C)A<AA=AB<BA=AB=BC<C
mjqxxxx '01

3
通常,在cstheory.stackexchange.com上不欢迎分配问题。
伊藤刚(Tsuyoshi Ito)

3
嗯,我不确定。通常,这里的逻辑是,回答典型的家庭作业问题不是一个好习惯,因为这样做会在将来破坏某人的家庭作业目的。但是在这种情况下,该问题看起来并不像典型问题。
伊藤刚(Tsuyoshi Ito)

2
也许,如果您给出的动机不同于“这是一项家庭作业”,人们可能会对此产生兴趣,并且不会被关闭。这可能有什么应用?
Marcos Villagra

2
关于重新解决问题,您可以忘记文件,并以这种方式查看它。您有一组成对的正整数,并且规则与您输入的规则相同。最初在第一列中排序,然后开始重命名点。A={(x1,y1),,(xn,yn)}
Marcos Villagra

Answers:


16

...我搜索了一些模式来减少NPC问题,但没有找到用“叉子”来表示“流”的方法。

所以(经过一些工作)这是一个多项式算法...

算法

起始列表可以视为个连续“ holes ” 的数组。对于每个初始对,将“ 元素 ”放在孔编号。可以将每对视为从位置到位置有向边。甲举动在于拾取元件在位置并将其移动到它的目的地位置(目的地孔变为不可移动的PEG)。我们删除边缘,然后继续选择下一个动作,该动作将从两个最接近的元素之一开始a jb jb j a j a j b j b j a j b j b k b j b j b kN2(aj,bj)bjajajbjbjajbjbk从位置(仅允许和之间的孔)。我们必须找到个连续动作的序列。bjbjbkN

  • 对于每个将(在数组位置)视为起始元素。b j a j s t a r t(aj,bj)bjajstart

    • 对于每个将视为最终元素(从位置到位置的边缘将成为最终边缘)。a k e n d a k b k(ak,bk),akajakendakbk

      • 使用以下条件从生成一系列移动,直到达到元素(并找到了解决方案)或停止条件e n dstartend

当您移动时,将钉固定在位置,并将数组分为两个分区(左)和(右),并且从到(或从到)的唯一方法是使用边穿过钉子。组 L R L R R LbjLRLRRL

  • edgesLR =左右边缘的数量(不计算最终边缘)
  • edgesRL =从右到左的边缘数(不计算最终边缘)
  • e d g e s L R - e d g e s R Lflow =edgesLRedgesRL

情况:

A)如果那么两个分区之一将变得无法访问,请停止|flow|>1

现在假设,即 Ë Ñ d řend>bjendR

B)如果那么从左到右还有一个额外的边,您必须向左走(选择的最近元素),否则您将永远无法到达L e n dflow=1Lend

C)如果那么从右到左会有一条额外的边,无论您选择哪个节点,都将永远不会到达,停止e n dflow=1end

D)如果,则必须向右走(选择的最近元素),否则就必须到达R e n dflow=0Rend

如果(),则B,C,D反转。 Ë Ñ d 大号end<bjendL

注意:向左或向右移动时,您必须将“作为钉子。例如,如果您必须向右走,但上最接近的元素是那么移动是不可能的(您必须继续进行另一对操作)R e n d s t a r t e n d endRend(start,end)

每一步都要施加相同的共振。

复杂

可以使用O(N)预先计算每个孔上的流量,并在每次扫描时重复使用。

循环是:

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     

在计算过程中没有任何选择,因此算法的复杂度为O(N3)

这是该算法的有效Java实现:

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);
    }    
}

这是一个有趣的方法。通常,每当您使用边,在每个方向上与交叉的未使用边的数量必须相等(或相差一个)。如果数字相差一个,就知道接下来必须走哪一条边。当数字相等时,您有一个选择,必须通过测试两个选项来解决。这似乎是一种足够有效的搜索策略,但是您如何知道最坏情况下的多项式呢?即,您怎么知道您只会遇到选择,其中每个方向上未使用的交叉边的数量相等?(a,b)bO(logn)
mjqxxxx

@mjqxxxx ...我重写了整个答案以匹配Java算法...
Marzio De Biasi

@mjqxxxx ...好的,最后我明白了... :-)
Marzio De Biasi

2
这看起来很正确,对我来说也很优雅。一旦使用了边,就无法再在 “行走” ;上剩下的唯一过渡是未使用的“ jumps”(有向边)穿过它,您必须使用所有这些过渡。如果指定了最后边缘,则需要将与缠绕在同一侧。这样,在每个边缘之后只有一个可能的方向可以行走,因为奇数(偶数)的跳跃会使您处于最初行走的另一侧(同一)。因此,可以在多项式时间内测试每个起点和终点的选择。(a,b)bb(an,bn)ban
mjqxxxx'2

1
这是一个漂亮的算法。我从来没有想过先解决最后的问题。次要点:(1)正如mjqxxxx所写,end必须为a_k。否则条件“ end> b_j”是错误的。(2)要么否定“流量”的定义,要么将案例B和C互换。
伊藤刚(Tsuyoshi Ito)

10

这不是解决方案,而是一种重新制定的格式,避免了显式提及交换和排序操作。首先对文件名及其交换版本的整个组合列表进行排序,然后在列表中标识每个文件名及其索引。如果两个文件之间的所有旧文件名都已被销毁,并且两个文件之间的新文件名尚未创建,则这两个文件为邻居文件。重新制定的问题如下:

给定不相交的有向边与的集合,是否有序这些边使得n(a,b)a,b{1,2,,2n}(a1,b1),(a2,b2),...,(an,bn)

  • 如果在和,则和ajbiai+1ji
  • 如果在和,则吗?b 一个+ 1 Ĵ + 1bjbiai+1ji+1

2
+1。这是陈述等效问题的简单得多的方法。只是一个澄清:边缘(a,b)是有方向的(就边缘(a,b)和边缘(b,a)的意义而言)。
伊藤刚(Tsuyoshi Ito)

@Tsuyoshi:谢谢;我编辑说是“导演”。
mjqxxxx 2011年

据我了解,短语“在和之间”表示。因此,我认为将前一种表示法更改为后一种表示法是值得的。一个ç 一个b Çbacabc
Oleksandr Bondarenko

@Oleksandr:这里的“ b在a和c之间”表示“ a <b <c或c <b <a。”
伊藤刚(Tsuyoshi Ito)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.