Matematické Fórum

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

#1 08. 05. 2010 11:06

awm1
Příspěvky: 51
Reputace:   
 

Domino

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#)

Code:

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.


Ruská ruleta: sudo [ $[ $RANDOM % 6 ] == 0 ] && rm -rf /

Moje stránky - http://www.klimesv.php5.cz

Offline

  • (téma jako vyřešené označil(a) byk7)

#2 08. 05. 2010 11:28

Pavel Brožek
Místo: Praha
Příspěvky: 5694
Škola: Informatika na MFF UK
Pozice: Student
Reputace:   194 
 

Re: Domino

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

 

#3 08. 05. 2010 12:46

awm1
Příspěvky: 51
Reputace:   
 

Re: Domino

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.


Ruská ruleta: sudo [ $[ $RANDOM % 6 ] == 0 ] && rm -rf /

Moje stránky - http://www.klimesv.php5.cz

Offline

 

#4 08. 05. 2010 15:11 — Editoval Kondr (08. 05. 2010 17:57)

Kondr
Veterán
Místo: Linz, Österreich
Příspěvky: 4246
Škola: FI MU 2013
Pozice: Vývojář, JKU
Reputace:   38 
 

Re: Domino

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).


BRKOS - matematický korespondenční seminář pro střední školy

Offline

 

#5 08. 05. 2010 17:24

Pavel Brožek
Místo: Praha
Příspěvky: 5694
Škola: Informatika na MFF UK
Pozice: Student
Reputace:   194 
 

Re: Domino

↑ 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

 

#6 08. 05. 2010 17:56

Kondr
Veterán
Místo: Linz, Österreich
Příspěvky: 4246
Škola: FI MU 2013
Pozice: Vývojář, JKU
Reputace:   38 
 

Re: Domino

↑ BrozekP: Jasně, napsal jsem blbost. Ale na nějaké dynamické programování by to vést mohlo.


BRKOS - matematický korespondenční seminář pro střední školy

Offline

 

#7 08. 05. 2010 20:05

awm1
Příspěvky: 51
Reputace:   
 

Re: Domino

↑ 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 :-).


Ruská ruleta: sudo [ $[ $RANDOM % 6 ] == 0 ] && rm -rf /

Moje stránky - http://www.klimesv.php5.cz

Offline

 

Zápatí

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson