Чи можна з'ясувати, чи існує послідовність у поліномі в часі в наступній задачі?


27

Я певний час думав над наступною проблемою, і не знайшов рішення для поліномів. Лише груба-чотирикласна. Я також намагався зменшити проблему NP-Complete, не маючи успіху.

Ось проблема :


У вас відсортований набір пар натуральних чисел. {(A1,B1),(A2,B2),,(An,Bn)}

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

Наступна операція може бути застосована до пари: Swap(pair). Він поміняє елементи пари, тому стане( 50 , 10 )(10,50)(50,10)

Коли пара в наборі поміняється, набір автоматично знову сортується (поміняється пара не виходить на місце, і він переміститься на своє місце в наборі).

Проблема полягає в тому, щоб побачити, чи є послідовність, яка, починаючи з якоїсь пари, підміняє весь набір із наступною умовою:

Після того, як пара поміняється, наступна пара, яку потрібно поміняти, повинна бути або спадкоємцем, або парою попередника в наборі.


Було б чудово знайти поліноміальне вирішення цієї проблеми або зменшення проблеми NP-Complete в ній.

Примітка.
Це вже проблема вирішення. Я не хочу знати, яка це послідовність: тільки якщо послідовність існує.

Приклад того, як сортується набір після заміни пари

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

Якщо я поміняю першу пару, вона стає на: , і після сортування набору (розміщення відсортованої пари у новій позиції) ми маємо:(5,6)

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

Тоді мені доведеться поміняти або пара (попередник), або (наступник), і повторювати процес, поки всі пари не будуть замінені (якщо можливо).( 7 , 8 )(3,4)(7,8)

Важливо:
Ви не можете замінити вже помінену пару.
Якщо є послідовність операцій "swap", то всі пари повинні бути перейменовані на один раз і лише один раз.

Приклад, коли неможливо замінити всі пари

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


1
Чи сортується список після перейменування файла та перед тим, як вибрати наступний файл для перейменування? Чи можете ви переписати умову сортування як: iff ( ) або ( і ) або ( і і )? A < A A = A B < B A = A B = B C < C (A,B,C)<(A,B,C)A<AA=AB<BA=AB=BC<C
mjqxxxx

3
Проблеми із призначенням загалом не вітаються на cstheory.stackexchange.com.
Цуйосі Іто,

3
Хм, я не впевнений. Зазвичай логіка тут полягає в тому, що відповідати на типові запитання домашніх завдань не є хорошою практикою, оскільки це знищить завдання домашнього завдання для когось у майбутньому. Але в цьому випадку проблема не виглядає як типова проблема.
Цуйосі Іто,

2
можливо, якщо ви дасте мотивацію, відмінну від "це було домашнє завдання", люди могли зацікавитись, і це не закриється. Що може бути можливим застосуванням цього?
Marcos Villagra

2
про переформулювання проблеми, ви можете забути про файли і побачити її таким чином. У вас є набір пар натуральних чисел , і правила такі ж, як і ви їх задали. Спочатку сортується в першому стовпчику, потім починаєш перейменовувати точки. A={(x1,y1),,(xn,yn)}
Маркос Віллагра

Відповіді:


16

... Я шукав деякі шаблони, щоб створити скорочення проблеми NPC, але не знайшов способу представити "потік" за допомогою "вилки" ...

Отже (після деякої роботи) це поліноміальний алгоритм ...

АЛГОРИТМ

Початковий список можна розглядати як масив послідовних " дірок ". Для кожної початкової пари поставте " елемент " на номер отвору . Кожну пару можна розглядати як спрямований край від положення до положення . Крок полягає у виборі елемент в положенні і переміщення його в становище призначення (отвір призначення стає нерухомим шпенька ). Видаляємо край і переходимо до вибору наступного кроку, який розпочнеться з одного з двох найближчих доступних елементів( a j , b j ) b j a j a j b j b j a j b j b k b j b j b k NN2(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

Випадки:

А) якщо тоді одна з двох секцій стане недосяжною, зупиніться|flow|>1

Тепер припустимо, що , тобто e n d Rend>bjendR

B) якщо то є додатковий край зліва направо, ви повинні піти вліво (виберіть найближчий елемент ), інакше ви ніколи не досягнетеL e n dflow=1Lend

C) , якщо , тобто додаткове ребро справа наліво і будь-який вузол ви вибираєте ви ніколи не досягнете , зупинкаe n dflow=1end

Г) якщо ви повинні піти праворуч (виберіть найближчий елемент ), інакше ви будете не доходити доR e n dflow=0Rend

Якщо ( ), B, C, D перевернуті. e n d Lend<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);
    }    
}

Це цікавий підхід. Загалом, щоразу, коли ви використовуєте ребро , повинно бути однакове (або різнитися на одне) число невикористаних ребер, що перетинають в кожному напрямку; і якщо цифри відрізняються один від одного, ви знаєте, який край потрібно взяти далі. Коли цифри рівні, у вас є вибір, який ви повинні вирішити, перевіривши обидва варіанти. Це здається достатньо ефективною стратегією пошуку, але як ти знаєш, що це поліном у гіршому випадку? Тобто, звідки ви знаєте, що ви будете стикатися лише з виборами коли кількість невикористаних країв перетину в кожному напрямку дорівнює? b O ( log n )(a,b)bO(logn)
mjqxxxx

@mjqxxxx ... Я переписав всю відповідь, щоб відповідати алгоритму Java ...
Marzio De Biasi

@mjqxxxx ... нормально, нарешті я це зрозумів ... :-)
Marzio De Biasi

2
(a,b)bb(an,bn)ban. Тоді існує лише один можливий напрямок прогулянки за кожним краєм, оскільки непарна (парна) кількість стрибків залишить вас на протилежному (такому ж) боці, яким ви спочатку ходили. Тому тестування кожного вибору вихідних і кінцевих ребер можна проводити в поліноміальний час.
mjqxxxx

1
Це прекрасний алгоритм. Мені ніколи не спадало на думку виправити останній крок спочатку. Незначні бали: (1) Як писав mjqxxxx, кінець повинен бути 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
  • bjbiai+1ji+1

2
+1. Це набагато простіший спосіб констатувати еквівалентну проблему. Лише одне уточнення: ребра (a, b) спрямовані (в тому сенсі, що край (a, b) і край (b, a) мають різні значення).
Tsuyoshi Ito

@Tsuyoshi: спасибі; Я редагував, щоб сказати "спрямований".
mjqxxxx

bacabc

@Oleksandr: Тут «Комерсант між а і з» означає «або <Ь <з або з <Ь <а.»
Цуоші Іто
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.