Olkoon yksinkertainen suuntaamaton, äärellinen graafi. Haluamme laskea kuinka monella tapaa :stä voidaan valita kaksi kaarta ja siten että näiden päätepisteiden joukot ja ovat pistevieraat. (Suuntaamaton kaarihan itse asiassa on päätepisteidensä joukko, joten asian voisi ilmaista myös niin, että kaaret ovat pistevieraat.)
Halutunlaisten kaariparien laskeminen voidaan tehdä käymällä läpi kaikki kaaret ja summaamalla kullekin kaarelle sen pariksi sopivien kaarien lukumäärä. Tämä lukumäärä kaarelle on .
Summan sieventämiseksi voidaan yhdistää saman lukumäärän antavat kaaret. Tätä varten muodostetaan graafista polynomi asettamalla jokaista kaarta vastaamaan termi , jossa eksponentti siis laskee kaaren päätepisteisiin liittyvien kaarien lukumäärän. Nämä summataan yhteen. Esimerkki:
Merkitään (huom. vakiotermi on , sillä jokaiseen kaareen liittyy ainakin yksi kaari nimittäin se itse). Nyt haluttu lukumäärä saadaan kätevästi:
Olisi voitu määritellä myös polynomi , asettamalla jokaiseen kaareen suoraan termi eli siihen liittymättömien kaarien lukumäärä. Tällöin haluttu summa saa helposti ilmaistavan muodon: .
Ja koska yleensä vielä halutaan että derivaatta pitää ottaa nollassa, olisi voitu tehdä siirto . Tällöin esimerkin polynomi olisi ollut ja kuten huomataan, tämän derivaatta nollassa eli ensimmäisen asteen kerroin on .
Vangin pasianssi on korttipeli, jossa tavallisesta 52 kortin pakasta valitaan ensin pöydälle 13 satunnaista korttia. Näistä samanarvoiset yhdistellään pinoiksi. Tämän jälkeen pakasta aletaan nostamaan kortteja, joiden avulla pöydän kortteja voi poistaa. Tämä tehdään nostamalla kerralla kolme korttia, joista vain viimeinen otetaan huomioon. Jos pöydällä on tämän kortin osoittaman arvon pino, se voidaan poistaa. Näin jatketaan, kunnes pakka on tyhjä (tai pöytä tyhjä). Peli menee läpi, mikäli pöytä saadaan tyhjäksi. Kysymys kuuluukin: Mikä on pelin läpimenemisen todennäköisyys?
Esimerkki pelitilanteesta, jossa pöydälle valittu kortit ja ensimmäinen kolmikko pakasta nostettu. Tässä katsotaan korttia risti neljä. Koska pöydällä ei ole nelosia, niin pöydältä ei poisteta mitään.
Voimme olettaa, että jälkimmäisessä (pakan 3 kerrallaan nostelussa) itse asiassa vain valitaan 13 satunnaista korttia. (Koska jokainen joka kolmas kortti on yhtä satunnainen kuin 13 päällimmäistäkin; sillä oletuksella että pakan alkuperäinen järjestys on satunnainen.) Läpimeno on yhtäpitävää sen kanssa, että jälkimmäinen valinta sisältää ainakin yhden kerran jokaisen pöydälle tulleen arvon.
Laskentaa hankaloittaa se, että pöydälle valitut 13 korttia vaikuttavat jäljellä olevan pakan valintojen todennäköisyyksiin. Tämän vuoksi meidän täytyy huomioida minkä tyyppinen valinta pöydälle on tullut. Määritellään tämä ”tyyppi” tarkasti ottaen pöydän pinojen kokojen (laskevasti) järjestettynä vektorina. Ylläolevassa esimerkkikuvassa on tullut tyyppi : 3 vitosta, 2 rouvaa, 1 kasi, 1 ässä, …, 1 kutonen. Tyypissä ei siis huomioida mitä kortteja on tullut, ainoastaan näiden lukumäärät. Tämä rajoittaakin mahdollisten tyyppien määrä huomattavasti: ne ovat itseasiassa luvun 13 ositukset osiin, joista jokainen on korkeintaan 4 (koska maita on 4, niin suurin mahdollinen pinon koko on 4). Ks. OEIS: http://oeis.org/A001400. Näitä on 39 kappaletta ja ne voi generoida seuraavalla Sage-koodilla:
#list of partitions of n to at most maxPartsN parts that are at most maxSize
def getPartitions(n, maxPartsN, maxSize):
if n==0: return [[]]
canMakeMax = maxPartsN*maxSize
if n>canMakeMax: return []
if n==canMakeMax: return [[maxSize]*maxPartsN]
ret = []
for x in xrange(min(n, maxSize), -1, -1):
ret += [[x] + sP for sP in getPartitions(n-x, maxPartsN-1, x)]
return ret
def getAllPossibleTypes(boardN, suits, vals):
return getPartitions(boardN, min(boardN, vals), suits)
a = getAllPossibleTypes(13, 4, 13)
print "{} types:\n{}".format(len(a), a)
Seuraavaksi meidän täytyy laskea mikä on kullekin tyypille todennäköisyys tulla pöydälle ja sitten mikä valita jäljellä olevasta pakasta 13 korttia siten, että jokaista tyypin korttia tulee ainakin yksi. Tässä onkin kätevää, että jälkimmäisessä valinnassa pakasta on poistettu tyypin mukaan juuri niitä kortteja, joita halutaan tulevan, joten sillä mitkä kortit ne olivat, ei ole väliä, vaan ainoastaan niiden tyypillä . Merkitään näitä kysyttyjä todennäköisyyksiä
Lasketaan ensin . Kuinka moni pakan 13-kombinaatioista on tyyppiä ? Lasketaan tulo
tässä valitaan jokaiselle :n komponentille arvo ja kerrotaan sillä kuinka monella tavalla tuon arvoiset kortit voidaan valita maiden joukosta. Huomaa: koska tyyppi on järjestetty vektori, arvojen valintoja ei jaeta :llä, mutta tulos täytyy jakaa sillä kuinka monella tavalla tyypin luvut voivat permutoida tyypin muuttumatta. Se on tyypin ”itsensä tyypin” yli laskettujen kertomien tulo , kun tyypin tyyppi on .
Sage-koodi näiden laskemiseen:
import fractions
from collections import Counter
def aProb(t, suits, vals):
num = 1
for i in xrange(len(t)):
num *= (vals-i)*binomial(suits, t[i])
num = num/reduce(lambda x,y: x*factorial(y), Counter(t).values(), 1)
denom = binomial(suits*vals, sum(t))
return fractions.Fraction(int(num), int(denom))
Sitten lukujen laskemiseen. Olkoon taas tyyppi . Nyt jäljellä oleva pakka on multijoukko , missä on tyypin komponenttia vastaavan kortin arvo ja . Tällaisen valinnan tekemisen todennäköisyyttä, että tiettyjä arvoja saadaan ainakin yksi, käsittelimme viime kerralla: https://membolicsythod.home.blog/2019/05/19/multijoukon-valintojen-todennakoisyyksia/ . Nyt vähintään kerran valituksi vaadittavat alkiot ovat . Kuten sanottu, sillä ei ole väliä mitkä nuo arvot ovat, vain niiden tyypillä.
Lopulta kysytty läpimenotodennäköisyys saadaan laskettua osina (ehdollistetaan sillä millainen tyyppi tulee, summa on kaikkien tyyppien yli):
Koottu, lopullisen vastauksen antava koodi:
from fractions import Fraction
from collections import Counter
R.<z> = QQ['z']
def waysToSelect(multiplicities, mustHave=[]):
g = R([1])
for j,nj in enumerate(multiplicities):
coeffs = [binomial(nj, k) for k in range(nj+1)]
if j in mustHave: coeffs[0] = 0 #subtracts the 1
g *= R(coeffs)
return [g[k] for k in range(g.degree()+1)]
#list of partitions of n to at most maxPartsN parts that are at most maxSize
def getPartitions(n, maxPartsN, maxSize):
if n==0: return [[]]
canMakeMax = maxPartsN*maxSize
if n>canMakeMax: return []
if n==canMakeMax: return [[maxSize]*maxPartsN]
ret = []
for x in xrange(min(n, maxSize), -1, -1):
ret += [[x] + sP for sP in getPartitions(n-x, maxPartsN-1, x)]
return ret
def getAllPossibleTypes(boardN, suits, vals):
return getPartitions(boardN, min(boardN, vals), suits)
def aProb(t, suits, vals):
num = 1
for i in xrange(len(t)):
num *= (vals-i)*binomial(suits, t[i])
num = num/reduce(lambda x,y: x*factorial(y), Counter(t).values(), 1)
denom = binomial(suits*vals, sum(t))
return Fraction(int(num), int(denom))
def bProb(t, suits, vals, pickN):
n = suits*vals
if len(t)==0 and pickN<=n: return Fraction(int(1), int(1))
if (len(t) and pickN==0) or pickN>n-sum(t): return Fraction(int(0), int(1))
deckMults = [suits-(t[i] if i<len(t) else 0) for i in range(vals)]
w = waysToSelect(deckMults, range(len(t)))
num = w[pickN] if len(w)>pickN else 0
denom = binomial(suits*vals-sum(t), pickN)
return Fraction(int(num), int(denom))
def winProb(suits, vals, boardN, pickN):
ret = Fraction(int(0), int(1))
n = suits*vals
if boardN+pickN > n: return ret
#for a type t, check if for some value all suits of that
#value have come and, as a result, we can't win
def someValueMaxed(t):
for x in t:
if x>=suits: return True
return False
ts = getAllPossibleTypes(boardN, suits, vals)
for t in ts:
if someValueMaxed(t): continue
ret += aProb(t, suits, vals)*bProb(t, suits, vals, pickN)
return ret
#the usual 4x13 deck, pick 13 in each phase of the game
p = winProb(4, 13, 13, 13)
print "{} \n= {}".format(str(p), float(p))
Olkoon multijoukko. (Alkio esiintyy kertaa.) Kun siitä valitaan alkiota, mikä on todennäköisyys, että joitain kiinnitettyjä alkoita, jotka numerointia muuttamalla voidaan olettaa olevan , saadaan kutakin vähintään yksi kappale? Valinta tehdään siten, että ajatellaan korttipakaksi, jossa jokaista -arvoista korttia on kappaletta (eli yhteensä korttia) ja pakka sekoitetaan (permutaatioiden tasajakauma), jonka jälkeen valitaan päällimmäistä korttia.
Pohditaanpa seuraavaa polynomien tuloa:
Kun tämän kertoo auki, tulee termin kertoimeksi lukumäärä kuinka monella tavalla voidaan valita alkiota kaikista :stä. Eli valitaan vain lukumäärät jokaiselle :lle. Tämä ei kuitenkaan ota huomioon sitä mitkä alkiot :stä :stä valitaan. Tätä varten kuhunkin tulon polynomiin on laitettava kertoimiksi binomikertoimet: . Mutta tämähän on binomikaavan mukaan , joka myös nähdäänkin nyt uudessa valossa: jokaisen yksittäisen :n kohdalla tehdään valinta otetaanko se mukaan () vai ei (). Koko tulo voidaan nyt kirjoittaa:
Eli tämä redusoituu edelleen tavallisiin binomikertoimiin , mikä onkin loogista sillä jokaista :tä pidetään erillisenä alkiona. Mutta tästä tullaankin siihen kysymykseen kuinka otamme huomioon, että jokaista , pitää olla valinnassa ainakin yksi. Se tapahtuu jättämällä tulossa vastaavista polynomeista vakiotermit pois (eli ei sallita alkiota valittavan nolla kertaa). Näin saadaan generoivaksi funktioksi
Seuraava Sage-koodi laskee :n kertoimet (multiplicities = luvut , mustHave = lista indekseistä, joita yllä merkittiin ):
R.<z> = QQ['z']
def waysToSelect(multiplicities, mustHave=[]):
g = R([1])
#n = sum(multiplicities)
for j,nj in enumerate(multiplicities):
coeffs = [binomial(nj, k) for k in range(nj+1)]
if j in mustHave: coeffs[0] = 0 #subtracts the 1
g *= R(coeffs)
return [g[k] for k in range(g.degree()+1)]
#an example: how to select from {0,0,1,1,1,2,3,3}
#so that have at least one 0 and at least one 1
a = waysToSelect([2,3,1,2], range(2))
print a
Tällä, kombinaatiot läpikäyvällä koodilla voi vielä tarkastaa tuloksen:
import itertools
def check(multiplicities, k, mustHave):
deck = []
for j, nj in enumerate(multiplicities): deck += [j]*nj
def isGood(a):
for x in mustHave:
if x not in a: return False
return True
goodCount = 0
totCount = 0
for hand in itertools.combinations(deck, k):
if isGood(hand): goodCount += 1
totCount += 1
return goodCount
mults = [2,3,1,2]
musts = range(2)
print [check(mults, k, musts) for k in range(sum(mults)+1)]
Todennäköisyydet saa, kun jakaa lukumäärät luvulla . Menetelmää voi myös yleistää valitsemalla kuhunkin tulon polynomiin vain ne termit, joita vastaavat lukumäärät kyseiselle alkiolle sallitaan valinnassa esiintyvän.
Tutkitaan binäärijonoja ja näissä olevia yhtenäisiä -putkia. Esimerkiksi jonossa on kaksi -putkea: yhden ja kolmen pituiset. Näistä kumpikaan ei ole parillisen pituinen, joten jono on halutun kaltainen. Merkitään tällaisten :n pituisten jonojen lukumäärää :llä.
Jono alkaa ja tämän voi tarkastaa esim. seuraavalla Python-koodilla:
def hasEven0Run(s):
run = 0
isRun = False
isOne = False
for c in s:
isOne = c=='1'
if isRun:
if isOne and run%2==0: return True
isRun = not isOne
if isRun: run += 1
else: run=0
return isRun and run>0 and run%2==0
def countBitStrings(n, tester):
count = 0
pad = n*'0'
for i in xrange(2**n):
s = (pad+(bin(i)[2:]))[-n:]
if tester(s): count += 1
return count
def getBitStrings(n, tester):
ret = []
pad = n*'0'
for i in xrange(2**n):
s = (pad+(bin(i)[2:]))[-n:]
if tester(s): ret.append(s)
return ret
runTester = lambda s: not hasEven0Run(s)
print [countBitStrings(n, runTester) for n in range(11)]
Kuinka sitten tämä lukumäärä voitaisiin laskea yleiselle ? Merkitään kaikkien halutunlaisten (kaikkien äärellisen pituisten) binäärijonojen joukkoa ja lisäksi sekä triviaalin symbolin yksiötä . Tällöin saadaan näiden joukkojen välille (rekursio-)yhtälö
joka taas johtaa suoraan :n generoivan funktion yhtälöön (huom. :n gen.funktio on )
Tästä voidaan ratkaista
Tämä rationaalifunktio voitaisiin jakaa osamurtohajotelmaan ja saada tarkka kaava (käyttämällä geometrisen summan kaavaa ) mutta asymptoottista kaavaa varten riittää tutkia :n lähimpänä origoa olevaa positiivista napaa. Tämä saadaan nimittäjän nollakohtana
Koska on nimittäjän yksinkertainen nollakohta eikä se ole osoittajan nollakohta, niin jonolle saadaan asymptoottinen kaava