Ketahui urutan dengan urutannya

18

pengantar

Misalkan Anda dan teman Anda sedang bermain game. Teman Anda memikirkan beberapa urutan nbit, dan tugas Anda adalah menyimpulkan urutan dengan mengajukan pertanyaan kepada mereka. Namun, satu-satunya jenis pertanyaan yang Anda boleh tanyakan adalah "Berapa lama urutan umum terpanjang dari urutan Anda dan S", di mana Sada urutan bit. Semakin sedikit pertanyaan yang Anda butuhkan, semakin baik.

Tugas

Tugas Anda adalah menulis sebuah program atau fungsi yang mengambil input bilangan bulat positif n, dan deretan Rpanjang biner n. Urutannya bisa berupa array bilangan bulat, string, atau jenis pilihan masuk akal lainnya. Program Anda akan menampilkan urutan R.

Program Anda tidak diizinkan untuk mengakses urutan Rsecara langsung. Satu- satunya hal yang diperbolehkan untuk dilakukan Radalah memberikannya sebagai input ke fungsi len_lcsbersama dengan urutan biner lainnya S. Fungsi len_lcs(R, S)mengembalikan panjang urutan umum terpanjang dari Rdan S. Ini berarti urutan bit terpanjang yang terjadi sebagai (tidak harus bersebelahan) berikutnya dalam keduanya Rdan S. Input len_lcsyang panjangnya mungkin berbeda. Program harus memanggil fungsi ini Rdan urutan lainnya beberapa kali, dan kemudian merekonstruksi urutan Rberdasarkan informasi itu.

Contoh

Pertimbangkan input n = 4dan R = "1010". Pertama, kita dapat mengevaluasi len_lcs(R, "110"), yang memberi 3, karena "110"merupakan urutan umum terpanjang dari "1010"dan "110". Kemudian kita tahu bahwa Rdiperoleh dari "110"dengan memasukkan satu bit pada posisi tertentu. Selanjutnya, kita mungkin mencoba len_lcs(R, "0110"), yang kembali 3karena urutan umum terpanjang adalah "110"dan "010", jadi "0110"tidak benar. Lalu kami coba len_lcs(R, "1010"), yang kembali 4. Sekarang kita tahu itu R == "1010", jadi kita bisa mengembalikan urutan itu sebagai output yang benar. Ini membutuhkan 3 panggilan ke len_lcs.

Aturan dan penilaian

Dalam repositori ini , Anda akan menemukan file bernama subsequence_data.txtberisi 100 urutan biner acak dengan panjang antara 75 dan 124. File-file tersebut dihasilkan dengan mengambil tiga float acak antara 0 dan 1, mengambil rata-rata mereka a, dan kemudian membalik-balik akoin nkali lipat. Skor Anda adalah jumlah ratalen_lcs - rata panggilan ke urutan ini, skor yang lebih rendah menjadi lebih baik. Kiriman Anda harus mencatat jumlah panggilan. Tidak ada batasan waktu, kecuali bahwa Anda harus menjalankan program Anda pada file sebelum mengirimkannya.

Kiriman Anda harus bersifat deterministik. PRNG diizinkan, tetapi mereka harus menggunakan tanggal hari ini, 200116(atau setara terdekat), sebagai benih acak. Anda tidak diizinkan untuk mengoptimalkan kiriman Anda terhadap kasus-kasus tes tertentu. Jika saya curiga ini terjadi, saya akan membuat batch baru.

Ini bukan kode golf, jadi Anda dianjurkan untuk menulis kode yang dapat dibaca. Rosetta Code memiliki halaman tentang urutan umum terpanjang ; Anda dapat menggunakannya untuk mengimplementasikan len_lcsdalam bahasa pilihan Anda.

Zgarb
sumber
Ide keren! Apakah ini punya aplikasi?
flawr
@ flawr Saya tidak tahu adanya aplikasi langsung. Idenya datang dari teori kompleksitas kueri , subbidang ilmu komputer, dan yang memiliki banyak aplikasi.
Zgarb
Saya pikir itu akan menjadi besar untuk memiliki tantangan yang sama lagi tapi di mana Anda dapat mengakses lcsbukannya len_lcs.
flawr
@ flawr Itu tidak terlalu menarik, karena lcs(R, "01"*2*n)kembali R. ;) Tapi itu bisa berhasil jika menelepon lcs(R, S)akan meningkatkan skor dengan len(S)alih-alih 1, atau sesuatu seperti itu ...
Zgarb
1
Saya ingin melihat jawaban lain = S
flawr

Jawaban:

10

Java, 99,04 98,46 97,66 lcs () panggilan

Bagaimana itu bekerja

Exaple: Baris kami yang akan direkonstruksi adalah 00101. Pertama-tama kita mencari tahu berapa banyak nol yang ada, dengan membandingkan (di sini membandingkan = menghitung lcs dengan string asli) dengan string hanya nol 00000. Kemudian kita melewati setiap posisi, membalikkan 0ke a 1dan memeriksa apakah sekarang kita memiliki substring yang lebih panjang. Jika ya, terima dan pergi ke posisi berikutnya, jika tidak, balikkan arus saat ini 1ke a 0dan pergi ke posisi berikutnya:

For our example of "00101" we get following steps:
input  lcs  prev.'best'
00000  3    0           //number of zeros
̲10000  3    3           //reject
0̲1000  3    3           //reject
00̲100  4    3           //accept
001̲10  4    4           //reject
0010̲1  5    4           //accept

Optimalisasi

Ini hanya implementasi "naif", mungkin mungkin untuk menemukan alogrithm yang lebih canggih memeriksa beberapa posisi sekaligus. Tapi saya tidak yakin apakah benar - benar ada yang lebih baik (misalnya berdasarkan perhitungan bit paritas mirip dengan kode Hamming), karena Anda selalu dapat hanya mengevaluasi panjang substring umum.

Untuk satu garis digit tertentu, algoritme ini perlu #ofDigitsUntilTheLastOccurenceOf1 + 1pemeriksaan persis . (Kurangi satu jika angka terakhir adalah 1.)

EDIT: Satu optimasi kecil: Jika kita baru saja memeriksa digit terakhir ke-2 dan kita masih perlu menyisipkan 1, kita tahu pasti bahwa itu harus berada di posisi terakhir, dan dapat menghilangkan centang yang sesuai.

EDIT2: Saya baru saja memperhatikan Anda dapat menerapkan ide di atas dengan yang terakhir k.

Tentu saja mungkin untuk mencapai skor yang sedikit lebih rendah dengan optimasi ini, dengan menata ulang semua lini terlebih dahulu, karena bisa jadi, bahwa Anda mendapatkan lebih banyak baris dengan yang di bagian paling akhir tetapi itu jelas akan menjadi dan optimasi untuk saat ini test case yang tidak lagi lucu.

Runtime

Batas atas adalah O(#NumberOfBits).

Kode lengkap

Berikut kode lengkapnya:

package jcodegolf;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

// http://codegolf.stackexchange.com/questions/69799/know-a-sequence-by-its-subsequences

public class SequenceReconstructor { 
    public static int counter = 0;
    public static int lcs(String a, String b) { //stolen from http://rosettacode.org/wiki/Longest_common_subsequence#Java
        int[][] lengths = new int[a.length()+1][b.length()+1];

        // row 0 and column 0 are initialized to 0 already

        for (int i = 0; i < a.length(); i++)
            for (int j = 0; j < b.length(); j++)
                if (a.charAt(i) == b.charAt(j))
                    lengths[i+1][j+1] = lengths[i][j] + 1;
                else
                    lengths[i+1][j+1] =
                        Math.max(lengths[i+1][j], lengths[i][j+1]);

        // read the substring out from the matrix
        StringBuffer sb = new StringBuffer();
        for (int x = a.length(), y = b.length();
             x != 0 && y != 0; ) {
            if (lengths[x][y] == lengths[x-1][y])
                x--;
            else if (lengths[x][y] == lengths[x][y-1])
                y--;
            else {
                assert a.charAt(x-1) == b.charAt(y-1);
                sb.append(a.charAt(x-1));
                x--;
                y--;
            }
        }

        counter ++;
        return sb.reverse().toString().length();
    }


    public static String reconstruct(String secretLine, int lineLength){
        int current_lcs = 0; 
        int previous_lcs = 0;
        char [] myGuess = new char[lineLength];
        for (int k=0; k<lineLength; k++){
            myGuess[k] = '0';
        }

        //find the number of zeros:
        int numberOfZeros = lcs(secretLine, String.valueOf(myGuess));
        current_lcs = numberOfZeros;
        previous_lcs = numberOfZeros;

        if(current_lcs == lineLength){ //were done
            return String.valueOf(myGuess);
        }


        int numberOfOnes = lineLength - numberOfZeros;
        //try to greedily insert ones at the positions where they maximize the common substring length
        int onesCounter = 0;
        for(int n=0; n < lineLength && onesCounter < numberOfOnes; n++){

            myGuess[n] = '1';
            current_lcs = lcs(secretLine, String.valueOf(myGuess));

            if(current_lcs > previous_lcs){ //accept

                previous_lcs = current_lcs;
                onesCounter ++;

            } else { // do not accept
                myGuess[n]='0';     
            }

            if(n == lineLength-(numberOfOnes-onesCounter)-1 && onesCounter < numberOfOnes){ //lets test if we have as many locations left as we have ones to insert
                                                                // then we know that the rest are ones
                for(int k=n+1;k<lineLength;k++){
                    myGuess[k] = '1';
                }
                break;
            }

        }

        return String.valueOf(myGuess);
    }

    public static void main(String[] args) {
        try {

            //read the file
            BufferedReader br;

            br = new BufferedReader(new FileReader("PATH/TO/YOUR/FILE/LOCATION/subsequence_data.txt"));

            String line;

            //iterate over each line
            while ( (line = br.readLine()) != null){

                String r = reconstruct(line, line.length());
                System.out.println(line);     //print original line
                System.out.println(r);        //print current line
                System.out.println(counter/100.0);  //print current number of calls
                if (! line.equals(r)){
                    System.out.println("SOMETHING WENT HORRIBLY WRONG!!!");
                    System.exit(1);
                }

            }


        } catch(Exception e){
            e.printStackTrace();;
        }

    }

}
cacat
sumber
1
Karena Anda mendapat lebih sedikit panggilan saat ada yang tertinggal 1s, sepertinya Anda bisa melakukan lebih baik secara rata-rata jika, setelah tebakan pertama memberi tahu Anda ada lebih banyak 0 daripada 1 yang Anda beralih ke berburu untuk 0 posisi daripada 1 posisi. Anda bahkan bisa melakukannya berkali-kali.
histokrat
1
@ histokrat Saya pikir dia sudah berhenti setelah dia menggunakan yang terakhir 1, yang setara dengan hanya ada nol yang tersisa.
Martin Ender