Funciton , tidak kompetitif
MEMPERBARUI! Peningkatan kinerja besar-besaran! n = 7 sekarang selesai dalam waktu kurang dari 10 menit! Lihat penjelasan di bawah!
Ini menyenangkan untuk ditulis. Ini adalah brute-force solver untuk masalah ini yang ditulis dalam Funciton. Beberapa factoids:
- Ia menerima integer pada STDIN. Setiap spasi putih asing memecahnya, termasuk baris baru setelah integer.
- Ia menggunakan angka 0 sampai n - 1 (bukan 1 ke n ).
- Itu mengisi grid "mundur", sehingga Anda mendapatkan solusi di mana baris bawah membaca
3 2 1 0
daripada di mana baris atas dibaca 0 1 2 3
.
- Ini menghasilkan dengan benar
0
(satu-satunya solusi) untuk n = 1.
- Output kosong untuk n = 2 dan n = 3.
- Ketika dikompilasi ke exe, dibutuhkan sekitar 8¼ menit untuk n = 7 (sekitar satu jam sebelum peningkatan kinerja). Tanpa mengkompilasi (menggunakan interpreter) dibutuhkan sekitar 1,5 kali lebih lama, jadi menggunakan kompiler sepadan.
- Sebagai tonggak pribadi, ini adalah pertama kalinya saya menulis seluruh program Funciton tanpa terlebih dahulu menulis program dalam bahasa pseudocode. Saya menulisnya di C # sebenarnya pertama meskipun.
- (Namun, ini bukan pertama kalinya saya melakukan perubahan untuk secara masif meningkatkan kinerja sesuatu di Funciton. Pertama kali saya melakukan itu dalam fungsi faktorial. Menukar urutan operan perkalian membuat perbedaan besar karena cara kerja algoritma multiplikasi . Jika Anda penasaran.)
Tanpa basa-basi:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Penjelasan dari versi pertama
Versi pertama membutuhkan waktu sekitar satu jam untuk menyelesaikan n = 7. Berikut ini sebagian besar menjelaskan bagaimana versi lambat ini bekerja. Di bagian bawah saya akan menjelaskan perubahan apa yang saya buat untuk mendapatkannya di bawah 10 menit.
Tamasya menjadi bit
Program ini membutuhkan bit. Perlu banyak bit, dan membutuhkannya di semua tempat yang tepat. Pemrogram Funciton berpengalaman sudah tahu bahwa jika Anda membutuhkan n bit, Anda dapat menggunakan rumus
yang dalam Funciton dapat dinyatakan sebagai
Ketika melakukan optimasi kinerja saya, terpikir oleh saya bahwa saya dapat menghitung nilai yang sama lebih cepat menggunakan rumus ini:
Saya harap Anda memaafkan saya bahwa saya tidak memperbarui semua grafik persamaan dalam posting ini.
Sekarang, katakanlah Anda tidak ingin blok bit yang berdekatan; sebenarnya, Anda ingin n bit secara berkala setiap k -th bit, seperti:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
Formula untuk ini cukup mudah setelah Anda mengetahuinya:
Dalam kode, fungsi tersebut Ӝ
mengambil nilai n dan k dan menghitung rumus ini.
Melacak nomor yang digunakan
Ada n ² angka dalam kisi terakhir, dan setiap angka dapat berupa n nilai yang mungkin. Untuk melacak nomor mana yang diizinkan dalam setiap sel, kami mempertahankan nomor yang terdiri dari n ³ bit, di mana bit diatur untuk menunjukkan bahwa nilai tertentu diambil. Awalnya angka ini adalah 0, jelas.
Algoritma dimulai di sudut kanan bawah. Setelah "menebak" angka pertama adalah 0, kita perlu melacak fakta bahwa 0 tidak lagi diizinkan di sel mana pun di sepanjang baris, kolom, dan diagonal yang sama:
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
Untuk tujuan ini, kami menghitung empat nilai berikut:
Baris saat ini: Kami perlu n bit setiap n bit th (satu per sel), dan kemudian beralih ke baris saat ini r , mengingat setiap baris berisi n ² bit:
Kolom saat ini: Kami membutuhkan n bit setiap n ²-th bit (satu per baris), dan kemudian menggesernya ke kolom saat ini c , mengingat setiap kolom berisi n bit:
Maju diagonal: Kami membutuhkan n bit setiap ... (apakah Anda memperhatikan? Cepat, cari tahu!) ... n ( n +1)-bit (dilakukan dengan baik!), Tetapi hanya jika kami benar-benar aktif maju diagonal:
Backward diagonal: Dua hal di sini. Pertama, bagaimana kita tahu kalau kita berada di diagonal mundur? Secara matematis, kondisinya adalah c = ( n - 1) - r , yang sama dengan c = n + (- r - 1). Hei, apakah itu mengingatkanmu pada sesuatu? Itu benar, itu pelengkap dua, jadi kita bisa menggunakan negasi bitwise (sangat efisien di Funciton) alih-alih pengurangan. Kedua, rumus di atas mengasumsikan bahwa kita ingin bit yang paling tidak signifikan untuk diatur, tetapi di diagonal mundur kita tidak, jadi kita harus menggesernya dengan ... apakah Anda tahu? ... Itu benar, n ( n - 1).
Ini juga satu-satunya di mana kita berpotensi membagi dengan 0 jika n = 1. Namun, Funciton tidak peduli. 0 ÷ 0 hanya 0, tahukah Anda?
Dalam kode, fungsi Җ
(yang terbawah) mengambil n dan indeks (dari mana ia menghitung r dan c berdasarkan pembagian dan sisanya), menghitung keempat nilai ini dan or
menyatukannya.
Algoritma brute-force
Algoritma brute-force diimplementasikan oleh Ӂ
(fungsi di atas). Dibutuhkan n (ukuran kisi), indeks (di mana dalam kisi kita saat ini menempatkan nomor), dan diambil (angka dengan n ³ bit memberi tahu kita nomor mana yang masih bisa kita tempatkan di setiap sel).
Fungsi ini mengembalikan urutan string. Setiap string adalah solusi lengkap untuk grid. Ini adalah pemecah yang lengkap; itu akan mengembalikan semua solusi jika Anda membiarkannya, tetapi mengembalikannya sebagai urutan malas yang dievaluasi.
Jika indeks telah mencapai 0, kami telah berhasil mengisi seluruh grid, jadi kami mengembalikan urutan berisi string kosong (solusi tunggal yang tidak mencakup sel). String kosong adalah 0
, dan kami menggunakan fungsi pustaka ⌑
untuk mengubahnya menjadi urutan elemen tunggal.
Pemeriksaan yang dijelaskan dalam peningkatan kinerja di bawah ini terjadi di sini.
Jika indeks belum mencapai 0, kami menurunkannya dengan 1 untuk mendapatkan indeks di mana sekarang kita perlu menempatkan nomor (sebut itu ix ).
Kami menggunakan ♫
untuk menghasilkan urutan malas yang berisi nilai dari 0 hingga n - 1.
Kemudian kami menggunakan ɓ
(ikatan monadik) dengan lambda yang melakukan hal berikut secara berurutan:
- Pertama-tama lihat bit yang relevan yang diambil untuk memutuskan apakah nomor tersebut valid di sini atau tidak. Kita dapat menempatkan nomor i jika dan hanya jika diambil & (1 << ( n × ix ) << i ) belum ditetapkan. Jika diatur, kembali
0
(urutan kosong).
- Gunakan
Җ
untuk menghitung bit yang sesuai dengan baris, kolom, dan diagonal saat ini. Bergeser dengan saya dan kemudian or
itu ke diambil .
- Panggilan secara rekursif
Ӂ
untuk mengambil semua solusi untuk sel yang tersisa, meneruskannya dengan yang baru diambil dan ix yang dikurangi . Ini mengembalikan urutan string tidak lengkap; setiap string memiliki karakter ix (kotak diisi hingga indeks ix ).
- Gunakan
ɱ
(peta) untuk pergi melalui solusi yang ditemukan dan gunakan ‼
untuk menggabungkan saya ke akhir masing-masing. Tambahkan baris baru jika indeks adalah kelipatan n , jika tidak spasi.
Menghasilkan hasilnya
Panggilan program utama Ӂ
(brute forcer) dengan n , index = n ² (ingat kita mengisi grid mundur) dan mengambil = 0 (awalnya tidak ada yang diambil). Jika hasil dari ini adalah urutan kosong (tidak ada solusi yang ditemukan), output string kosong. Kalau tidak, output string pertama dalam urutan. Perhatikan bahwa ini berarti ia hanya akan mengevaluasi elemen pertama dari urutan, itulah sebabnya pemecah tidak melanjutkan sampai ia menemukan semua solusi.
Peningkatan performa
(Bagi mereka yang sudah membaca versi lama dari penjelasan: program tidak lagi menghasilkan urutan urutan yang perlu secara terpisah diubah menjadi string untuk output; itu hanya menghasilkan urutan string secara langsung. Saya telah mengedit penjelasan sesuai Tapi itu bukan peningkatan utama. Ini dia.)
Di komputer saya, exe yang dikompilasi dari versi pertama hanya membutuhkan waktu 1 jam untuk menyelesaikan n = 7. Ini tidak dalam batas waktu yang diberikan 10 menit, jadi saya tidak beristirahat. (Yah, sebenarnya, alasan aku tidak beristirahat adalah karena aku punya ide tentang cara mempercepatnya secara masif.)
Algoritme seperti dijelaskan di atas menghentikan pencarian dan backtracks setiap kali menemukan sel di mana semua bit dalam jumlah yang diambil ditetapkan, menunjukkan bahwa tidak ada yang dapat dimasukkan ke dalam sel ini.
Namun, algoritme akan terus mengisi grid dengan sia-sia hingga ke sel tempat semua bit diatur. Akan jauh lebih cepat jika bisa berhenti begitu setiap sel yang belum diisi sudah memiliki semua bit yang ditetapkan, yang sudah menunjukkan bahwa kita tidak akan pernah bisa menyelesaikan sisa grid tidak peduli berapa nomor yang kita masukkan saya t. Tetapi bagaimana Anda secara efisien memeriksa apakah ada sel yang memiliki n bit yang diset tanpa melalui semuanya?
Caranya dimulai dengan menambahkan satu bit per sel ke nomor yang diambil . Alih-alih apa yang ditunjukkan di atas, sekarang tampilannya seperti ini:
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
Alih-alih n ³, sekarang ada n ² ( n +1) bit dalam angka ini. Fungsi yang mengisi baris / kolom / diagonal saat ini telah diubah sesuai (sebenarnya, sepenuhnya ditulis ulang untuk jujur). Fungsi itu masih akan mengisi hanya n bit per sel, jadi bit tambahan yang baru saja kita tambahkan akan selalu 0
.
Sekarang, katakanlah kita setengah jalan dalam perhitungan, kita baru saja menempatkan sebuah 1
di sel tengah, dan jumlah yang diambil terlihat seperti ini:
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
Seperti yang Anda lihat, sel kiri atas (indeks 0) dan sel kiri tengah (indeks 10) sekarang tidak mungkin. Bagaimana kita menentukan ini dengan paling efisien?
Pertimbangkan angka di mana bit ke-0 dari setiap sel diatur, tetapi hanya sampai indeks saat ini. Angka seperti itu mudah dihitung menggunakan rumus yang sudah dikenal:
Apa yang akan kita dapatkan jika kita menambahkan dua angka ini bersama?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
Hasilnya adalah:
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
Seperti yang Anda lihat, penambahan melimpah ke bit ekstra yang kami tambahkan, tetapi hanya jika semua bit untuk sel itu ditetapkan! Karenanya, yang harus dilakukan hanyalah menutupi bit-bit itu (rumus yang sama seperti di atas, tetapi << n menutup ) dan periksa apakah hasilnya 0:
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
Jika bukan nol, kisi tidak mungkin dan kita bisa berhenti.
- Tangkapan layar menampilkan solusi dan waktu berjalan untuk n = 4 hingga 7.