Nevíte-li si rady s jakýmkoliv matematickým problémem, toto místo je pro vás jako dělané.
Nástěnka
❗22. 8. 2021 (L) Přecházíme zpět na doménu forum.matweb.cz!
❗04.11.2016 (Jel.) Čtete, prosím, před vložení dotazu, děkuji!
❗23.10.2013 (Jel.) Zkuste před zadáním dotazu použít některý z online-nástrojů, konzultovat použití můžete v sekci CAS.
Nejste přihlášen(a). Přihlásit
Ahoj,
zase ten CodEx...máme úlohu ze vstupní sady kostiček domina složit co nejdelší řadu (kostky se mohou přikládat vždy jen způsobem odpovídající čísla k sobě). Řešení jsem napsal, a to hned tři, problém je v tom, že všechna nejsou dostatečně rychlá – ani jedno mi neprojde posledním testovacím vstupem (který bohužel neznám). Tady je (paradoxně) nejrychlejší z nich, které vždy vybere nějakou kostku (žádná dobrá heuristika mne nenapadla), zkusí ji připojit do dosavadního řetězce, a když to jde, tak rekurzivně zavolá stejný algoritmus na vzniklou delší řadu + kratší seznam zbylých prvků: (kód je v C#)
using System; using System.Collections.Generic; using System.Text; namespace Domino { class Kostka //jedna dominová kostka { public byte pole1, pole2; public Kostka(byte pole_1, byte pole_2) { pole1 = pole_1; pole2 = pole_2; } public void Otoc() //Otočí čísla kostky. { byte tmp = pole1; pole1 = pole2; pole2 = tmp; } public static bool operator == (Kostka kostka1, Kostka kostka2) //Uvažuje i rovnost stejných, avšak vzájemně otočených kostek. { return (((kostka1.pole1.Equals(kostka2.pole1)) && (kostka1.pole2.Equals(kostka2.pole2))) || ((kostka1.pole1.Equals(kostka2.pole2)) && (kostka1.pole2.Equals(kostka2.pole1)))); } public static bool operator !=(Kostka kostka1, Kostka kostka2) //Uvažuje i nerovnost stejných, avšak vzájemně otočených kostek. { return !(((kostka1.pole1.Equals(kostka2.pole1)) && (kostka1.pole2.Equals(kostka2.pole2))) || ((kostka1.pole1.Equals(kostka2.pole2)) && (kostka1.pole2.Equals(kostka2.pole1)))); } public static bool KostkyMajiAsponJednoShodnePole(Kostka kostka1, Kostka kostka2) //Oznamuje, že kostky mají alespoň jedno shodné pole s číslem. { return ((kostka1.pole1.Equals(kostka2.pole1)) || (kostka1.pole2.Equals(kostka2.pole2)) || (kostka1.pole1.Equals(kostka2.pole2)) || (kostka1.pole2.Equals(kostka2.pole1))); } } class Nacitac { private bool JeCislice(int znak) //Hlásí, zda vstupní znak je číslice. { return ((znak >= (int)'0') && (znak <= (int)'9')); } private byte NactiCislo() //Načte jedno číslo ze vstupu. { int vystup = 0; int prectenyZnak; //aktuálně přečtený znak do //Přeskoč všechny nečíselné znaky: { prectenyZnak = Console.Read(); } while (!JeCislice(prectenyZnak)); while (JeCislice(prectenyZnak)) //Čti číslo, dokud jsou na vstupu číslice: { vystup = vystup * 10 + (prectenyZnak - (int)'0'); prectenyZnak = Console.Read(); } return (byte)vystup; } public List<Kostka> NactiVstup(out byte pocetKostek) //Načte kostky na vstupu a vrátí je v poli kostek. Kostky, jejichž čísla nemají žádnou další do páru, jsou automaticky vyřazeny. { //System.IO.FileStream vstupniSoubor = new System.IO.FileStream("domino.in", System.IO.FileMode.Open, System.IO.FileAccess.Read); //System.IO.StreamReader vstup = new System.IO.StreamReader(vstupniSoubor); //Console.SetIn(vstup); pocetKostek = NactiCislo(); List<Kostka> kostkyZeVstupu = new List<Kostka>(); for (int i = 0; i < pocetKostek; i++) { kostkyZeVstupu.Add(new Kostka(NactiCislo(), NactiCislo())); } Kostka zpracovavanaKostka; bool maMoznehoSouseda; for (int i = 0; i < pocetKostek; i++) //Vyřazení kostek, které nejdou připojit k žádné jiné. { maMoznehoSouseda = false; zpracovavanaKostka = kostkyZeVstupu[i]; for (int j = 0; j < pocetKostek;) { if (i == j) {j++; continue;} if(Kostka.KostkyMajiAsponJednoShodnePole(zpracovavanaKostka, kostkyZeVstupu[j])) maMoznehoSouseda = true; j++; } if (maMoznehoSouseda == false) kostkyZeVstupu.Remove(zpracovavanaKostka); } pocetKostek = (byte) kostkyZeVstupu.Count; return kostkyZeVstupu; } } class Resitel //Instance této třídy bude mít za úkol najít pomocí rekurze řešení úlohy. { static int counter = 0; public void NajdiMaxRetezec(byte predchoziCislo, List<Kostka> zbyvajiciKostky, ref byte celkovyPocetKostek, ref int delkaMaxRady, ref bool hotovo, Resitel resitel) { int delkaAktualniRady = 0; //délka maximální řady pro aktuální způsob volby prvků List<Kostka> zpracovaneKostky = new List<Kostka>(); //zatím zpracované kostky List<Kostka> naslednici = zbyvajiciKostky.FindAll(delegate(Kostka kostka) { return ((predchoziCislo == kostka.pole1) || (predchoziCislo == kostka.pole2)); }); //Nalezení všech potenciálních následníků kostky. foreach (Kostka zpracovavanaKostka in naslednici) { if (zpracovaneKostky.Contains(zpracovavanaKostka)) continue; //Pokud již byla stejná kostka někdy zpracovávána, nemusím se současnou už dále zabývat a přejdu na další iteraci. zpracovaneKostky.Add(zpracovavanaKostka); //Nakonec je zpracovávaná kostka vložena do seznamu již zpracovaných kostek. byte noveCisloNaKonciRady = (predchoziCislo == zpracovavanaKostka.pole1) ? zpracovavanaKostka.pole2 : zpracovavanaKostka.pole1; //Zjisti nové číslo na konci řady. List<Kostka> zbyleKostky = resitel.VyradKostku(zbyvajiciKostky, zpracovavanaKostka); delkaAktualniRady = celkovyPocetKostek - zbyleKostky.Count; if (zbyleKostky.Count != 0) NajdiMaxRetezec(noveCisloNaKonciRady, zbyleKostky, ref celkovyPocetKostek, ref delkaMaxRady, ref hotovo, resitel); //Pokud nebyly vyčerpány všechny kostky, pokus se najít lepší řešení. else { delkaMaxRady = delkaAktualniRady; hotovo = true; } if (hotovo == true) break; if (delkaAktualniRady > delkaMaxRady) //Pokud bylo nalezeno delší řešení než dosavadní: { delkaMaxRady = delkaAktualniRady; if (delkaMaxRady == celkovyPocetKostek) hotovo = true; //Pokud bude nalezen řetězec o délce odpovídající počtu všech kostek, můžeme hned skončit. } else { if (++counter > (11000 * celkovyPocetKostek)) hotovo = true; }; if (hotovo == true) break; } } public List<Kostka> VyradKostku(List<Kostka> seznamZbyvajicichKostek, Kostka vyrazovanaKostka) //Vytvoří nový seznam kostek, který už nebude obsahovat zadanou kostku. { List<Kostka> vyslednySeznam = new List<Kostka>(); //vytvoření nového seznamu kostek vyslednySeznam.AddRange(seznamZbyvajicichKostek); //zkopírování obsahu původního seznamu vyslednySeznam.Remove(vyrazovanaKostka); //odstranění požadované kostky return vyslednySeznam; } } class Program { static void Main(string[] args) { Nacitac nacitac = new Nacitac(); byte celkovyPocetKostek; List<Kostka> kostkyZeVstupu = nacitac.NactiVstup(out celkovyPocetKostek); Resitel resitel = new Resitel(); int delkaMaxRady = 1; //dosud maximální nalezená délka List<Kostka> zpracovaneKostky = new List<Kostka>(); //zatím zpracované kostky bool hotovo = false; //Pokud bude nalezen řetězec o délce odpovídající počtu všech kostek, můžeme hned skončit. foreach (Kostka zpracovavanaKostka in kostkyZeVstupu) { if (zpracovaneKostky.Contains(zpracovavanaKostka)) continue; //Pokud již byla stejná kostka někdy zpracovávána, nemusím se současnou už dále zabývat a přejdu na další iteraci. zpracovaneKostky.Add(zpracovavanaKostka); //Nakonec je zpracovávaná kostka vložena do seznamu již zpracovaných kostek. List<Kostka> zbyvajiciKostky = resitel.VyradKostku(kostkyZeVstupu, zpracovavanaKostka); //Vytvoření seznamu kostek, ze kterého vyřadíme už zvolenou první kostku. resitel.NajdiMaxRetezec(zpracovavanaKostka.pole2, zbyvajiciKostky, ref celkovyPocetKostek, ref delkaMaxRady, ref hotovo, resitel); if (delkaMaxRady == celkovyPocetKostek) break; //Pokud bude nalezen řetězec o délce odpovídající počtu všech kostek, můžeme hned skončit. zpracovavanaKostka.Otoc(); //Nyní zpracovávanou kostku otočím a pokusím se s ní znovu vyhledat co nejdelší řetězec. resitel.NajdiMaxRetezec(zpracovavanaKostka.pole2, zbyvajiciKostky, ref celkovyPocetKostek, ref delkaMaxRady, ref hotovo, resitel); if (delkaMaxRady == celkovyPocetKostek) break; //Pokud bude nalezen řetězec o délce odpovídající počtu všech kostek, můžeme hned skončit. } if (celkovyPocetKostek == 0) Console.Write(0); //vypsani vysledku else Console.Write(delkaMaxRady); } } }
Zkoušel jsem odstranit všechny List<Kostka> a nahradit je poli + jejich přímou indexací (protože List má lineární dobu přístupu k prvku), ale ani to mi nepomohlo. Dokonce nepomohlo ani aplikovat ten postup, že si u kostek zavedu "délku" a jednotlivé kostky se v rekurzi pokouším slučovat - tj. vezmu dvě kostky, které k sobě lze přiložit a udělám z nich jednu o součtu "délek" těch dvou předchozích. To šlo ještě pomaleji.
Nemáte někdo nějaký nápad, jak celý algoritmus ještě urychlit? Ideální by byl dle mých představ nějaký algoritmus na hledání cesty přes všechny vrcholy, procházející nutně zadanými hranami (což by byly hrany mezi čísly na kostce). Nejhorší ale je, že mne (kromě vyřazení zcela jasných případů) nenapadá žádný obecný způsob, jak určit, že nalezená řada kostek je už nutně maximální. (Obávám se ale, že on asi ani žádný nebude.)
Jinak tuhle úlohu berte spíše jako pro zamyšlení se mnou - jde mi jen o tři body do plného počtu bodů ze všech úkolů z programování, takže ji už vlastně ani nemusím řešit.
Předem díky za odpověď.
V.
Offline
Kód jsem nečetl. Chápu dobře, že na vstupu máš množinu kostek, každá kostka je dána dvěma čísly?
Jenom takový nápad, nevím, jestli je to správně a jestli to může být rychlé:
Čísla na kostkách jsou vrcholy grafu, dominové kostky pak hrany mezi vrcholy. Hledal bych cykly v grafu. Jakmile bych nějaký cyklus našel, všechny dominové kostky, které ho tvoří, bych si dal někam mimo a hledal bych další cykly. Když bych už měl pouze graf bez cyklů (nebudou tam už ani násobné hrany, to by byl jinak cyklus, takže graf nebude mít ani moc hran), našel bych v něm všechny maximální cesty (nevím, jestli se to tak označuje, prostě cesty, které nelze prodloužit). Pro ty bych pak hledal, jaké všechny cykly se na ně dají "přilepit".
Offline
Ahoj,
ano, každá kostka je dána dvěma čísly (z rozsahu 1-38) a je jich maximálně 20. Čísla na kostkách se mohou všelijak kombinovat a opakovat.
Jenom mi není jasná tvá grafová reprezentace domina. Jestli dobře chápu, pro každé číslo je v něm jen jeden vrchol a hrany reprezentují kostky, tj. graf vlasně obsahuje všechna možná položení kostek za sebe. Vida, to mne vůbec nenapadlo. Algoritmust jako takový vypadá docela dobře, a dle mého názoru (nedokazoval jsem si) by mohl běžet rychleji než můj dosavadní. Jenom mám trochu obavu, že hlavně to hledání cyklů bude docela sranda – nakreslil jsem si pár příkladů a je mi jasné, že celá operace bude zahrnovat ošetření celkem hodně podmínek (vnořené cykly v dalších cyklech budou dělat slušný nepořádek).
Jinak ale moc díky, až se k tomu dostanu, pokusím se to implementovat.
V.
PS: Další nápady jsou však vítány, už jsem nad tím proseděl dost dlouho a fakt už netuším, jak dál.
Offline
EDIT: Tohle nefunguje, viz příspěvek od BrozekP níže.
Tož vyrobme tabulku, jejíž buňka na pozici (a,b) (a je řádek, b sloupec) nám říká, jaká nejdelší sekvence používající pouze prvních a kostek (ne nutně všechny) končí číslem b. A výstup je maximum z posledního řádku. A protože každý řádek vznikne z předchozího dvěma operacemi, stačí si pamatovat poslední řádek. Čas se nám tím smrskne na O(38+20) a paměť na O(38).
Offline
↑ Kondr:
Dejme tomu, že budeme postupně přidávat kostky (1,2), (3,4) a (2,3) v tomto pořadí. Pak třetí řádek přece nemůže vzniknout pouze dvěma operacemi. Jestli dobře chápu, jak jsi tabulku myslel, tak by měla vypadat takto:
Rozumím tomu dobře?
Offline
↑ BrozekP: Jasně, napsal jsem blbost. Ale na nějaké dynamické programování by to vést mohlo.
Offline
↑ Kondr:
Tenhle postup ale asi opravdu ne – pokud je mi to jasné, musel bych v každém řádku stejně vyzkoušet všechny možnosti tvorby řady a výsledky předchozích řádků by mi byly na dvě věci – na zabrání paměťi a dost možná i zpomalení výpočtu :-).
Offline