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
Předem díky.
Čtu Průvodce labyrintem algoritmů a v kapitole 1., cvičení 5. jsem narazil na problém.
Můj algoritmus není schopen splnit podmínku „… který si vystačí s čísly omezenými n-násobkem výsledku.“
Problém je, že mé řešení je vždy omezeno velikostí samotného výsledku a nejsem schopen odhalit ten (méně efektivní) algoritmus splňující zadání.
Uvažoval jsem i o kompozici polynomu, různých roznásobeních apod., ale vše se ukazalo liché.
Algoritmus
Slovní popis
1. Stanovuje se výpočet čitatele kratší o max((n-k), k).
2. Z min((n-k), k) se získávají součinitelé.
3. Pro i = max((n-k), k) + 1 až n
a1. Stanovuje se max. součinitel z kroku 2 soudělný k aktuálnímu mezivýsledku. (Pokud takový existuje.)
a2. Mezivýsledek se krátí nalezeným součinitelem. (Pokud byl nalezen.)
a3. Takový součinitel je vyřazen ze seznamu součinitelů, protože byl již vykrácen.
b1. Jako a1, ale pro aktuální i.
b2. Jako a2, ale pro aktuální i.
b3. Jako a3, ale pro aktuální i.
c. Nový mezivýsledek je roven součinu jeho (pokracené) velikosti s (pokraceným) i.
4. Vrací se mezivýsledek
Řešení v C#
public void KombinačníČíslo(int n, int k) { // TODO: Známé výsledky nepočítat, tj. n nad n, k = 1, k = 0 apod. (int referent, int dělitel) jmenovatel = default; // Velikosti bez faktoriálu. int jmenovatelVariacíBezFaktoriálu = n - k; if (jmenovatelVariacíBezFaktoriálu > k) // Faktoriál čitatele se počítá od max((n-k), k) + 1. { jmenovatel.referent = jmenovatelVariacíBezFaktoriálu; jmenovatel.dělitel = k; } else { jmenovatel.referent = k; jmenovatel.dělitel = jmenovatelVariacíBezFaktoriálu; } var součiniteléVeJmenovateli = SoučiniteléVeJmenovateli(); // Mezivýsledek je rovnou max((n-k), k) + 1. (Ušetří se obrátka 1 × max((n-k), k) + 1.) Console.WriteLine($" n {n}, k {k}; { Výsledek(jmenovatel.referent + 1, jmenovatel.referent + 2, n) }"); // Rozpad na součinitele. List<int> SoučiniteléVeJmenovateli() { var součinitelé = new List(jmenovatel.dělitel); for (var i = jmenovatel.dělitel; i > 1; --i) // Jedna se vynechává. { součinitelé.Add(i); } return součinitelé; } int koef; long Výsledek(long result, int current, in int limit) { Console.Write(result); int dělitelResultu = součiniteléVeJmenovateli.Where(svj => result % svj == 0).MaxOrDefault(); if (dělitelResultu != default) { result /= dělitelResultu; // Krátí se mezivýsledek (v čitateli). součiniteléVeJmenovateli.Remove(dělitelResultu); } Console.WriteLine(" " + result); Console.Write(current); int dělitelCurrentu = součiniteléVeJmenovateli.Where(svj => current % svj == 0).MaxOrDefault(); if (dělitelCurrentu == default) { koef = current; } else { koef = current / dělitelCurrentu; // Krátí se aktuální číslo z čitatele. součiniteléVeJmenovateli.Remove(dělitelCurrentu); } Console.WriteLine(" " + koef); if (current > limit) { return result; } return Výsledek(checked(result * koef), ++current, in limit); } }
Moje logika
Vyjděme z příkladu 11 nad 7, kdy výsledek je 330. Omezení mezivýsledku je tedy 11 × 330 = 3630.
1. 11! je 39 916 800. Čitatel tedy musí být krácen.
2. 7! je 5040. Musí tedy být použit menší z součinitelů jmenovatele pro výpočet jeho výpočet (a druhý pro krácení čitatele).
3. Zbývá tedy 8 × 9 × 10 × 11 / 4 × 3 × 2 (4!).
4. 8 × 9 × 10 × 11 = 7920. Tedy i zbytek čitatele musí být krácen.
5. Po vykrácení je součin 2 × 3 × 5 × 11 = 330. Což je ovšem už výsledek.
Můj problém
1. Pakliže je mezivýsledek omezen n-násobkem výsledku, lze říci, že pro poslední mezivýsledek, platí, že ve jmenovateli zbylo právě n. Kde by se tam vzalo?
2. Jak zaručím, že čitatel bude dostatečně pokrácen, aby splnit omezení n-násobku výsledku a zároveň byl pokrácen dostačně málo, abych spolehlivě stanovil jeho max. velikost n × výsledek?
Offline
↑ Uživatel919:
Ahoj. Ja bych na to sel napr. tak, ze
Pri vypoctu bych tedy zacal "z druhe strany" :
Offline
↑ laszky:
Dobře, ale když se začne na lichém čísle, řekněme pro 15 nad 3.
1. 13
2. 13 ÷ 2 = 6,5
3. 91 ÷ 3 = 30,3333…
4. 455.
Mělo by se to počítat v celočíselné proměnné. Je to spolehlivé a tuším, že i rychlé. Navíc i kdybych zkontroval, jestli je číslo soudělné a případně krátil až po součinu, tak pořád nikde nevidím omezení n × výsledek. V tomto případě 455 × 15 = 6825. Číslo, které není přítomno ani vzdáleně.
Trochu tabulek
352716 ÷ 7 = 50388
19 × 50388 = 957372 (n × výsledek)
957372 ÷ 352716 = 2,7142…
19 × 27132 = 515508 (n × výsledek)
Nicméně díky za vzorečky. Nevěděl jsem, že platí, že ((n nad k) (n nad k) × (n + 1)) % (k + 1) = 0. Předpokládám, že je zužitkuji.
Offline
↑ Uživatel919:
Ahoj, zkratis jeste pred soucinem:
Omezeni je 1 x vysledek. :-)
Neni zac (ty vzorecky).
Offline
Naimplementoval jsem algoritmus dle nových postupů, ale bohužel algoritmus je méně efektivní, resp. dříve přeteče proměná. Např. pro (65 nad 33) už nefunguje. Jednoduše má větší mezivýsledky.
public void Spočti(int n, int k) { // TODO: Známé výsledky nepočítat, tj. n nad n, k = 1, k = 0 apod. (int početOpakování, int velikostKrácení) počtyJmenovatele = default; int jmenovatelVariacíBezFaktoriálu = n - k; if (jmenovatelVariacíBezFaktoriálu > k) { počtyJmenovatele.velikostKrácení = jmenovatelVariacíBezFaktoriálu; počtyJmenovatele.početOpakování = k; } else { počtyJmenovatele.velikostKrácení = k; počtyJmenovatele.početOpakování = jmenovatelVariacíBezFaktoriálu; } bool kráceno = false; // První obrátku není třeba počítat. Console.WriteLine($" n {n}, k {k}; { Výsledek(počtyJmenovatele.velikostKrácení + 1, 2, počtyJmenovatele.velikostKrácení + 2, počtyJmenovatele.početOpakování) }"); int auxCurrent; long Výsledek(long result, int count, int current, in int limit) { if (count > limit) { return result; } Console.Write(current); if (current % count == 0) { auxCurrent = current / count; kráceno = true; } else { auxCurrent = current; } Console.WriteLine(" " + auxCurrent); Console.Write(result); if (!kráceno && result % count == 0) { result /= count; kráceno = true; } Console.Write(" " + result); result = checked(result * auxCurrent); if (!kráceno && result % count == 0) { result /= count; } Console.WriteLine(" " + result); kráceno = false; return Výsledek(result, ++count, ++current, in limit); } }
Porovnání pro (47 nad 27)
Vpravo nový algoritmus. Na posledním řádku s vypočty vpravo je v prostřed mezivýsledek, který je ovšem u původního algoritmu maximální, což zde neplatí.
Pořád mi uniká, proč by měly být mezivýsledky omezeny n-násobkem výsledku. Otázku nechám otevřenou.
Offline
↑ laszky:
Úplně nerozumím, ale počítá se vždy v obou algoritmech s tím menším (n-k), k, tedy min((n-k), k). Proto pro n = 47 a k = 27, se to počítá jako
.
Je to vidět i tabulkách, kde jsou ze začátku čísla 28, 29.
Offline