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 25. 04. 2010 14:26

awm1
Příspěvky: 51
Reputace:   
 

Dynamické programování - loupežníci

Ahoj,

řeším úkol nazvaný Loupežníci – dva loupežníci mají nakradené předměty o cenách >=1 (ceny jsou různé a mohou se i opakovat) a chtějí si je rozdělit dle cen přesně napůl (přičemž jednotlivé předměty nelze dělit). Pro každý vstup (počet předmětů a jejich ceny) mám najít libovolné správné řešení, popř. oznámit, že pro zadaný vstup řešení neexistuje (tj. lichý součet cen, nemožnost sestavit množinu přesně poloviční ceny než je celková cena.)

Úlohu jsem řešil tak, že jsem po odfiltrování zaručeně neřešitelných vstupů (viz výše) použil obměnu algoritmu na řešení problému batohu, přičemž jsem zkoušel najít takovou podmnožinu předmětů na vstupu, která by dohromady dávala přesně polovinu součtu všech cen. Všechno mi funguje, jak má, jenže v našem univerzitním testovacím systému to prostě neprojde předposledním testem - výpočet prošvihne časový limit. Přitom dynamickým programováním bych měl řešení najít dost rychle.

Proto se chci zeptat, zda by vás někoho při pohledu na kód dole nenapadla nějaká optimalizace. Už jsem odstranil všechno, co by jakkoliv mohlo zpomalovat výpočet, a furt nic. Zkoušel jsem i najít nějakou techniku, jak úlohu vyřešit rychleji, avšak rychlejší jsou prý už jen heuristické analýzy vstupu, které navíc ani nemusí dát vždy optimální výsledek.

Ještě bych zapomněl, že program je v C# a celkový součet cen prvků na vstupu a ani jejich počet prý nepřekročí 2 000 000. Jinak u této úlohy nejde ani tak o paměťovou složitost (pro svůj běh dostane řádově desítky MB operační paměti), ale spíš o tu časovou (nevím kolik, ale limit bude tak 2-3 sekundy).

A teď ten kód:

Code:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using CodEx;

namespace Loupeznici
{
    class Nacitac
    {
        Reader reader;

        public Nacitac()
        {
            reader = new Reader("poklad.in");
        }
        
        private bool JeCislice(int znak)
        {
            return ((znak >= (char)'0') && (znak <= (char)'9'));
        }

        public int[] NactiVstup(out int soucetPrvku)
        {
            soucetPrvku = 0;
            int pocetPrvku = this.reader.Int();
            if (pocetPrvku == 0) throw new Exception("Autor si ze mne dělá legraci, ale já se jen tak nedám!");
            string zahodit = this.reader.Line();
            int[] vystup = new int[pocetPrvku + 1];
            int nacteneCislo = 0;
            for (int i = 1; i <= pocetPrvku; i++)
            {
                nacteneCislo = this.reader.Int();
                if (nacteneCislo < 1) throw new Exception("Autor si ze mne dělá legraci, ale já se jen tak nedám!");
                vystup[i] = nacteneCislo;
                soucetPrvku += nacteneCislo;
            }
            return vystup;
        }
    }

    class Loupeznici
    {
        static void Main(string[] args)
        {
            //inicializace výstupu
            FileStream vystupni_soubor = new FileStream("poklad.out", FileMode.Create, FileAccess.Write);
            StreamWriter vystup = new StreamWriter(vystupni_soubor);
            try
            {
                Nacitac nacitac = new Nacitac();
                int hledanySoucet = 0;
                int[] poklad = nacitac.NactiVstup(out hledanySoucet);
                //Pokud je vstup prázdný nebo součet prvků nelze dělit dvěma beze zbytku, nemá smysl dále počítat.
                if ((poklad.Length == 0) || ((hledanySoucet % 2) != 0)) throw new Exception("prazdny vstup nebo zarucene neexistujici reseni");
                else
                {
                    hledanySoucet /= 2;
                    int pocetPredmetu = poklad.Length - 1;
                    int[] iteracniPole = new int[2000001];
                    iteracniPole[0] = -1;
                    //int counter = 0;
                    for (int k = 1; k <= pocetPredmetu; k++)
                    {
                        for (int i = hledanySoucet; i >= poklad[k]; i--)
                        {
                            if ((iteracniPole[i - poklad[k]] != 0) && (iteracniPole[i] == 0))
                            {
                                iteracniPole[i] = k;
                                //counter++;
                                if (i == hledanySoucet) goto skok;
                            }
                        }
                    }
                    skok:
                    if (iteracniPole[hledanySoucet] == 0) throw new Exception("Reseni nenalezeno."); //Největší možné zaplnění není optimální.
                    int nalezenyMaxSoucet = hledanySoucet;
                    //while (iteracniPole[nalezenyMaxSoucet] == 0) { nalezenyMaxSoucet--; }
                    while (iteracniPole[nalezenyMaxSoucet] != -1)
                    {
                        vystup.Write("{0} ", iteracniPole[nalezenyMaxSoucet]);
                        nalezenyMaxSoucet = nalezenyMaxSoucet - poklad[iteracniPole[nalezenyMaxSoucet]];
                    }
                }
            }
            catch //Pokud jakákoliv fáze výpočtu ukazuje na neexistenci řešení, je vyhozena výjimka, která je zde "ošetřena" vypsáním informace o neexistenci řešení.
            {
                vystup.Write("no");
            }
            finally //Nakonec je třeba soubor uzavřít.
            {
                vystup.Close();
            }
        }
    }
}

BTW: Ten nepodmíněný skok tam není, to se vám jen zdá. :-)

Díky za radu.

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 25. 04. 2010 21:40

RePRO
Místo: Jihlava
Příspěvky: 363
Škola: AI VŠPJ (09-12, Bc.)
Pozice: programátor
Reputace:   11 
Web
 

Re: Dynamické programování - loupežníci

↑ awm1:

Ahoj, na kód jsem se moc nedíval.

1. Dělal jsem třeba časovou problematiku prvočísel. A tam třeba bylo, že počet průchodů (FOR cyklus) stačí procházet jenom do půlky. (v Tvém případě by mohlo být něco podobného...)

2. Vytváříš tam (celkem) velké pole (new). V Tvém případě pak TY podmínky a veškeré operace s polem zase vše zahlcují (a nabývají na čase). Ale tím si asi nepomůžeš.

No já bych to opravdu viděl jenom asi v tom FOR cyklu.


Srdcem trochu-programátor, duší rádoby-matematik a povoláním analytik-vývojář.

Offline

 

#3 25. 04. 2010 22:00

awm1
Příspěvky: 51
Reputace:   
 

Re: Dynamické programování - loupežníci

↑ RePRO:
Ahoj,

ad 1. - Právě v tom vidím největší problém – já už tam totiž např. mám okamžité ukončení výpočtu při nalezení optimálního řešení (kdyby to tam nebylo, udělal by program klidně i o polovinu více kroků než teď, mám to otestováno), jenže můj algoritmus se k němu očividně nedokáže vždy dostat přijatelně rychle. A z principu dynamického programování všechny předchozí kroky musím udělat (pokud si dobře pamatuji), protože nevím, které se mi budou hodit při tvorbě konečného řešení.

ad 2. - Zkusím to alokovat dynamicky podle načteného počtu prvků a součtu jejich cen, ale také se obávám, že to asi zrovna moc nepomůže.

Jinak ale díky za radu.

V.

BTW: Nevíte někdo, jak najít optimální řešení problému batohu rychleji než s pomocí dynamického programování?


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

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

Offline

 

#4 26. 04. 2010 21:07

check_drummer
Příspěvky: 5181
Reputace:   106 
 

Re: Dynamické programování - loupežníci

Ahoj, a nebude náhodou problém batohu NP úplný?


"Máte úhel beta." "No to nemám."

Offline

 

#5 26. 04. 2010 23:03

awm1
Příspěvky: 51
Reputace:   
 

Re: Dynamické programování - loupežníci

↑ check_drummer:
Ahoj,

ano, problém batohu je NP-complete. Mne šlo spíše o to, jestli existuje nějaký lepší algoritmus než použití DP, a pokud ne, tak zda to, co jsem napsal, nedělá někde něco zbytečně pomalu/navíc.

Nakonec to už mám vyřešeno – spolužák mi poradil, že načtenou posloupnost cen je velmi výhodné seřadit dle velikosti od největší. Nepřišlo by mi, že by to mělo nějak pomoci, ale fakt to funguje. Takže "pro budoucí generace": mé řešení je (alespoň z hlediska CodExu) správné, jen si v něm musíte seřadit načtený vstup. A aby nebylo zapomenuto původní pořadí jednotlivých cen, doporučuji si napsat strukturu, jejíž instance budou obsahovat dvojici cena - její původní pořadové číslo na vstupu; načítací metoda pak bude vracet seřazené pole těchto struktur – k seřazení použijete normální Array.Sort(poleCenAIndexů), přičemž této metodě dáte jako další parametr komparátor, který bude řadit prvky pole podle ceny ve strukturách. K tomu stačí napsat vhodný delegát, ve kterém zapouzdříte metodu CompareTo na porovnávání objektů, přičemž ta vám bude porovnávat ceny předmětů.

V.


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

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

Offline

 

#6 29. 04. 2010 18:56

check_drummer
Příspěvky: 5181
Reputace:   106 
 

Re: Dynamické programování - loupežníci

Třeba bych se zaměřil na různé "typy" vstupů a podle klasifikace typu bych použil některý z "předpřipravených" algoritmů.
Např. pokud $\sum_{ai=1}^{k-1}a_i < a_k$ pro všechna k (po eventuálním setřídění), lze součet podmnožiny (a tedy i problém loupežníků) řešit v polynomiálním čase.


"Máte úhel beta." "No to nemám."

Offline

 

Zápatí

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson