Olkoon lunnollinen luku. Valitaan :stä satunnainen hilaneliö (kärjet kokonaisluku-pisteissä). Huom. neliö voi olla myös vinossa (ks kuva). Mikä on tämän neliön alan odotusarvo?
Ratkaisu
Jokaisella hilaneliöllä on sitä ympäröivä akseleiden suuntainen neliö, joten hilaneliöt voidaan käydä läpi käymällä läpi nämä ympäröivät neliöt ja jokaiselle ympäröivälle neliölle sen sisällä olevat neliöt. Nämä sisäneliöt saadaan ”kiertämällä” eli liikuttamalla kutakin neliön kärkeä ympäröivän neliön sivua pitkin (ks. havainnollistus)
ympäröivä neliö sinisellä katkoviivalla, sen sisällä olevien hilaneliöiden läpikäynti animoitu
Näin saadaan ensinnäkin hilaneliöiden lukumääräksi
Pöydällä on -neliöön aseteltuna kolikkoa. Jokainen kolikko on joko kruuna (merk. 1) tai klaava (merk. 0). Kuviosta löytyvä alineliö on kruunaneliö, jos sen kaikki kolikot ovat kruunoja.
Esimerkki, jossa n=5 ja suurin löytyvä kruunaneliön sivu on 2.
Kuviosta etsitään suurin mahdollinen kruunaneliö. Kysymys kuuluu: kuinka monta kolikoiden konfiguraatiota on, joissa suurin löytyvä kruunaneliö on sivultaan ? Merkitään tätä lukumäärää .
Ratkaisu
Tutkitaan kahta laskutapaa: Inkluusio-ekskluusio ja Markovin ketju. Näistä ensimmäinen toimii kun on suuri ja jälkimmäinen, kun on pieni (2 tai 3). Kumpikaan ei toki kovin suurille toimi. Suurin tapaus, jonka saamme näillä keinoin kokonaan ratkaistua on .
Inkluusio-ekskluusio
Lasketaan -kolikkoneliöiden lukumäärä, joista löytyy (ainakin) -kruunaneliö. Merkitään tätä lukumäärää . (Kysytty saadaan sitten .) Tätä varten tehdään määritelmä (jossa indeksointi aloitetaan nollasta)
niiden neliöiden joukko, joissa on k-sivuinen kruunaneliö lähtien indeksistä (i, j),
Esimerkki joukosta . Tässä n=6 ja k=3.
Nythän ja inkluusio-ekskluusio kaavan mukaan saadaan
Leikkauksen koko saadaan laskettua selvittämällä vastaavan kolikkojen yhdisteen koko , missä on kyseisen alineliön (sen, jonka vaaditaan olevan kruunaneliö) kolikot. Kaikkien tämän yhdisteen kolikkojen on siis oltava kruunoja, kun taas loput saavat olla mitä vaan, joten
Joukkojen lukumäärän kasvaessa läpikäytävien joukkojen lukumäärä kasvaa hyvin nopeasti. Eri kokoisia yhdisteitä on kuitenkin maksimissaan kappaletta. Jos saman yhdistekoon tuottavien -joukkojen lukumäärän laskulle olisi tehokas keino, paranisi tämä algoritmi huomattavasti. Kun yhdisteessä on joukkoa, niin suurille r yhdisteen koko näyttäisi olevan suurinpiirtein normaalijakautunut (paitsi loppupäässä, jossa koko on rajoitettu :lla; kaikkien () yhdisteen kokohan on tietysti ). Tätä tutkintalinjaa ei kuitenkaan jatkettu pitemmälle.
Markovin ketju
Ajatellaan että kolikkoneliö muodostetaan rivi kerrallaan. Pidetään jokaiselle sarakkeelle kirjaa, kuinka pitkä putki kruunoja siinä on tullut. Riittää rajoittaa putken pituudeksi eli sen yli ei mennä vaikka tulisikin kruuna. Tiloina on siis kaikki n-vektorit , missä ( kappaletta) ja siirtymät näiden välillä tehdään mahdollisien rivien ( kappaletta) avulla. Lisäksi on yksi absorboiva tila (kutsutaan lopputilaksi), johon siirrytään, jos k-sivuinen kruunaneliö on muodustunut. Kruunaneliön muodustuminen pystytään päättelemään sarakkeiden kruunaputkista: kun uusi rivi lisätään, kasvatetaan putkia ja nyt annetaan putken kasvaa aina :hon asti. Kruunaneliö syntyy, jos tästä putkivektorista löytyy -putki, jossa jokainen arvo on . Jos kruunaneliö syntyi, siirrytään lopputilaan. Jos taas ei, niin rajoitetaan putket taas korkeintaan :een ja siirrytään syntyneeseen tilaan.
Tämähän ei oikeastaan ole Markovin ketju, sillä siirtymämatriisiin ei laiteta todennäköisyyksiä, vaan tämän siirtymän tuottavan rivien lukumäärä. Tämän takia esim. lopputilan itsesiirtymäänkin laitetaan arvo . Lähtötila on nollavektori ja koko kolikkoneliö saadaan lisäämällä riviä, joten lukumäärä saadaan siirtymämatriisin :nnen potenssin paikasta, joka vastaa tiloja (alkutila, lopputila).
Otetaan esimerkiksi . Tällöin tilat ovat (merkitään lopputilaa (-1, -1, -1))
”Siirtymä”-matriisi, joka laskee 3×3 kolikkoneliöiden, joista löytyy 2×2 kruunaneliö, lukumäärän
Koodit
Seuraavassa yllä kuvaillut algoritmit toteutettuna SageMath-koodina. Funktio f(n, k) valitsee sopivamman algoritmin riippuen parametreistä n ja k.
import itertools
def fMarkov(n, k):
def hasRun(s):
run = 0
for x in s:
if x==k:
run += 1
if run==k: return True
else:
run = 0
return False
rows = list(itertools.product(*[[0,1]]*n))
states = list(itertools.product(*[range(k) for _ in range(n)]))
startState = (0,)*n
endState = (-1,)*n
states.append(endState)
stateToMatInd = {s: i for i,s in enumerate(states)}
startI = stateToMatInd[startState]
endI = stateToMatInd[endState]
mat = matrix(ZZ, len(states), sparse=False)
for i1, s1 in enumerate(states[:-1]):
for r in rows:
s2 = tuple( min(k, s1[j]+1) if r[j]==1 else 0 for j in range(n))
if hasRun(s2):
mat.add_to_entry(i1, endI, 1)
else:
s2 = tuple(min(k-1, x) for x in s2)
mat.add_to_entry(i1, stateToMatInd[s2], 1)
mat.add_to_entry(endI, endI, len(rows))
return (mat**n)[startI][endI]
def fIE(n, k):
squares = [(i,j) for i in range(n-k+1) for j in range(n-k+1)]
squareSets = {(i,j): set((i+i1, j+j2) for i1 in range(k)
for j2 in range(k)) for i,j in squares}
sizeForUSize = {u: 2**(n**2-u) for u in range(n**2+1) }
tot = 0
for r in range(1, len(squares)+1):
for J in itertools.combinations(squares, r):
U = set().union(*[squareSets[t] for t in J])
tot += (-1)**(r+1) * sizeForUSize[len(U)]
return tot
def f(n, k):
if k>n: return 0
if k==n: return 1
if k==0: return 2**(n**2)
if k==1: return 2**(n**2)-1
if k==2 and n<14 : return fMarkov(n, k)
if k==3 and n<9: return fMarkov(n, k)
return fIE(n, k)
import time
startTime = time.time()
n = 6
fVals = [f(n, k) for k in range(n+1)]
#print (fVals)
print ([fVals[k]-(fVals[k+1] if k<n else 0) for k in range(n+1)])
print ("Time: ", time.time()-startTime)
Tulokset
n
k=0
k=1
k=2
k=3
k=4
k=5
k=6
k=7
k=8
3
1
416
94
1
4
1
42175
22913
446
1
5
1
15701272
17364903
486337
1918
1
6
1
21418970800
45575022927
1716842688
8632385
7934
1
7
1
107224417436159
434119929848225
21481265058911
124196411584
144634177
32254
1
8
1
1968910887183791358
15477075016864949170
994438116039714639
6311672705366016
8378550282432
2365317953
130046
1
Lukumäärät h(n, k)
Arvo (eli nxn-matriisien lukumäärä, joista löytyy (ainakin) 2×2 kruunaneliö) on OEIS-jono A255938. Funktio fMarkov() kykenee laskemaan vielä termin
Tutkitaan nyt kolikkosuorakaidetta. Olkoon lisäksi jokainen kolikko painotettu, niin että kruunan todennäköisyys on . a) Olkoon . Mikä on ensimmäinen , jolle todennäköisyys, että suorakaiteesta löytyy kruunaneliö, on yli 0.5? b) Mille arvolle todennäköisyys kruunaneliön löytymiselle suorakaiteesta on 0.25? c) Oletetaan, että saat aluksi valita suorakaiteen pituuden . Mutta tämän myötä (kaikkien kolikoiden kruuna-tn) muuttuu kaavan mukaan. Miten kannattaa valita, jotta kruunaneliön löytymisen todennäköisyys maksimoituu?
Inkluusio-ekskluusio koodissa yhdiste lasketaan jokaiselle -joukolle uudestaan. Tätä voisi parannella: generoidaan -joukot itse ja pidetään aina siihen mennessä syntynyt yhdiste muistissa. Ks. myös OEIS-linkin kaavaa (eikö se tosin ole hyvin epätehokas, koska siinä on vielä tulo J-joukon potenssijoukon yli??)
Olkoon annettu kolmio, jonka kantakulmat ovat terävät. Kolmion sisälle on asetettava akselien suuntainen suorakaide, jonka yläpuolelle (ja myös kolmion sisälle) ympyrä. Tehtävänä on maksimoida suorakaiteen ja ympyrän alojen summa.
Asetetaan kolmio koordinaatistoon seuraavalla tavalla ja tehdään kuvassa olevat merkinnät.
Merkitään lisäksi kolmion kannan pituutta :llä ja sivujen pituuksia sekä kolmion piiriä . Merkitään vielä kärjen x-koordinaattia :lla (sen y-koordinaattihan on korkeus ).
Ratkaistaan suorakaiteen x-rajat ja . Yhdenmuotoisista kolmioista saadaan
ja
Näin ollen suorakulmion vaakasivu on .
Ratkaistaan sitten ympyrän säde. Maksimitapauksessahan ympyrä on alkuperäisen kolmion sivujen ja suorakaiteen yläsivun rajoittaman kolmion , sisään piirretty ympyrä, joten käytetään sille löytyvää kaavaa
,
missä on :n piirin puolikas ja sen sivut. Yksi sivu on suorakaiteen vaakasivu ja toiset saadaan alkuperäisen kolmion sivuista kertomalla suhdeluvulla (käytetään taas yhdenmuotoisia kolmioita). Jokaisessa termissä on siis kertoimena tämä suhdeluku. Nimittäjään jää sen neliö. Siis
Toisaalta Heronin kaavan mukaan kolmion ala on , joten saamme
Näin saamme suorakaiteen ja ympyrän yhteiselle alalle funktion
Toisen asteen kerroin on , (ks. Tehtävä 1). Näin ollen paraabeli on alaspäin aukeava. Derivaatan nollakohdasta löydämme maksimikohdan
.
Tehtäviä
Osoita, että kolmiolle, jonka kanta on , korkeus ja piiri pätee .
Osoita että edellä vasen puoli voidaan vielä kertoa 2:lla ja epäyhtälö säilyy. Kenties vielä kolmella… Mikä on suurin luku, jolla epäyhtälö vielä on voimassa?
Tutkitaan :n pituisia jonoja symboleista ja . Kun tällainen mielletään kävelyksi, määräytyy sille kunkin askeleen jälkeen korkeus, jolla kävely on (lähdetään nollasta). Kävelylle lasketaan vaihteluväli erotuksena , missä on kävelyn korkeus askeleen jälkeen.
Kaksi esimerkkikävelyä ja niiden vaihteluvälin lasku. Tässä .
Kysymys: kuinka monta kävelyistä on vaihteluväliltään korkeintaan , missä on annettu parametri.
Ratkaisu
Tehdään ensin kokeilua varten algoritmi, joka käy kaikki kävelyt (s.o. -1, 1 -jonot) läpi. Kielenä SageMath
def brw(r, n):
count = 0
for w in cartesian_product([(-1,1)]*n):
ss = [sum(w[:j]) for j in range(n+1)]
if max(ss)-min(ss) <= r: count += 1
return count
for r in range(7): print ([brw(r, n) for n in range(10)])
Huomataan, että arvoille r = 2, r = 3, r = 4 ja r = 5 jono löytyy OEIS:sta. Sieltä löytyy generoivat funktiot ja tapaukselle kaava
brw(2, n) =
Myös tapauksille on OEIS:ssa lähes yhtä siistit kaavat (niissä esiintyy Fibonacci-luvut).
Koska emme kuitenkaan keksi tästä yleistä kaikille toimivaa kaavaa, ryhdytään kehittämään tehokkaampaa algoritmia. Kun jonoa luetaan alusta lähtien ja tarkastellaan korkeutta, jolla ollaan askeleen jälkeen, niin mieleen tulee tietenkin Markovin ketju suunnattu verkko, jossa tilana solmuina on . Tämä ei kuitenkaan itsessään riitä, vaan tilaan tarvitaan myös tieto siitä missä maksimi- ja minimikorkeuksissa on käyty siihen mennessä. Siirtymät tietenkin tehdään vain sallituissa rajoissa. Seuraava Sage-koodi tekee tämän ja laskee kävelyjen määrän siirtymämatriisin . potenssista (summataan lähtötilaa vastaava rivi).
def brw(r, n):
bds = [(i,j) for i in range(-r, 1) for j in range(r+1) if j-i<=r]
states = [] #triples (i, j, k) where (i,j) are the min, max -heights so far and k is current height
for (i,j) in bds:
states += [(i,j,k) for k in range(i, j+1)]
matN = len(states)
startState = states.index((0,0,0))
arr = [[0]*matN for _ in range(matN)]
for ind1, (i,j,k) in enumerate(states):
i2 = i-1 if k-1<i else i
if j-i2<=r:
s2 = (i2, j, k-1)
arr[ind1][states.index(s2)] += 1
j2 = j+1 if k+1>j else j
if j2-i<=r:
s2 = (i, j2, k+1)
arr[ind1][states.index(s2)] += 1
return sum((Matrix(arr)**n)[startState])
Tilojen määrä kuitenkin kasvaa nopeasti, kun r kasvaa (), joten tutkitaan verkon rakennetta ja huomataan siitä laskemista helpottavia rekursioita.
Piirretään verkko koodilla
def getBrwMat(r):
bds = [(i,j) for i in range(-r, 1) for j in range(r+1) if j-i<=r]
states = []
for (i,j) in bds:
states += [(i,j,k) for k in range(i, j+1)]
matN = len(states)
ret = [[0]*matN for _ in range(matN)]
for ind1, (i,j,k) in enumerate(states):
i2 = i-1 if k-1<i else i
if j-i2<=r:
s2 = (i2, j, k-1)
ret[ind1][states.index(s2)] += 1
j2 = j+1 if k+1>j else j
if j2-i<=r:
s2 = (i, j2, k+1)
ret[ind1][states.index(s2)] += 1
return Matrix(ret), states
myR = 3
a, states = getBrwMat(myR)
startState = states.index((0,0,0))
g = DiGraph(a)
vColors = {}
heights = {}
for r,color in enumerate(['blue', 'purple', 'pink', 'orange', 'red']):
vsOfH = [j for j,s in enumerate(states) if s[1]-s[0]==r]
vColors[color] = vsOfH
heights[r] = vsOfH
g.show(figsize=8, vertex_size=70, vertex_labels=None,
vertex_colors=vColors, iterations=700,
layout='ranked', heights=heights)
Tutkittava verkko, kun . Lähtötila on alhaalla.
Verkon rakenteellahan on selvä merkitys: ”putket” ovat sen hetkisiä (min, max) -rajoja. Putkessa voi ”seilata” edes takaisin ja päästä siirtyä aina seuraavaan putkeen eli siirtää (min, max) -rajoja yhdellä. Kunnes max-min = r, jolloin vaihteluväli ei saa enää kasvaa, ja jäädään seilaamaan siihen -putkeen, johon päädyttiin. Huom. ”putki” on itse asiassa verkkona (suuntaamaton) polku.
”Putki” eli polkuverkko
Keskitytään aluksi yhteen -putkeen ja sen kävelyihin eli yllä mainittuun ”seilaamiseen”. Merkitään :lla ensimmäisestä päästä () lähtevien kävelyjen määrää pituisessa putkessa, jotka päätyvät putken solmuun . Huomaa, että tämä on tietysti symmetrinen, jos putki käännetään.
Tutkitaan sitten koko verkkoa ja tehdään hieman yleisempi määritelmä: brwRec(m, r, n) = kävelyiden määrä verkolla, jossa ensimmäinen putki on -pituinen ja lähdetään tämän putken (jommasta kummasta) päästä. Huomaa, että haluttu lukumäärä on tällöin erikoistapaus brw(r, n) = brwRec(1, r, n). Nyt kävely voi seilata ensimmäisessä putkessa jonkun aikaa ja siirtyä sitten, päätyessään jompaan kumpaan päätyyn, seuraavalle tasolle, jolle jää loput askeleet (-1 sillä myös tasolle siirtymä kuluttaa yhden askeleen). Kävely voi myös olla siirtymättä seuraavalle tasolle ja päätyä lopuksi mihin tahansa putken somuun. Saadaan siis rekursioyhtälö
Koodataan nämä rekursiot ja käytetään memoisatiota:
pMemo = {}
def p(m, j, n):
if n==0: return 1if j==0 else 0
memoKey = (m,j,n)
if memoKey in pMemo:
return pMemo[memoKey]
ret = 0
if j>0: ret += p(m, j-1, n-1)
if j<m-1: ret += p(m, j+1, n-1)
pMemo[memoKey] = ret
return ret
brwMemo = {}
def brwRec(m, r, n):
if n==0: return 1
memoKey = (m,r,n)
if memoKey in brwMemo:
return brwMemo[memoKey]
ret = 0
if r>0:
for j in range(n):
ret += (p(m, 0, j) + p(m, m-1, j)) * brwRec(m+1, r-1, n-1-j)
ret += sum(p(m, j, n) for j in range(m)) #stay on the first path
brwMemo[memoKey] = ret
return ret
brw = lambda r,n: brwRec(1, r, n)
Tämä algoritmi toimii jo kohtuullisessa ajassa suurehkoillekin r:n arvoille, mutta tässä ongelmaksi tulee suuret n:n arvot. Matriisimenetelmässä taas suuret n:n arvot eivät tuottaneet ongelmaa, koska matriisin potenssiinkorotus on nopeaa (). Koitetaan saada molempien maailmojen paremmat puolet ja käytetään keskikokoisille :n ja :n arvoille rekursiivista metodia ja pienille :n arvoille matriisimenetelmää. Arvolle voidaan käyttää myös valmista kaavaa. Koska ratkaisujen lukuarvot kasvavat nopeasti hyvin suuriksi, annetaan testin vuoksi vastaukset jossain suuressa modulossa, esim 7347249713. Tehdään laskenta siis renkaassa . Tämä onnistuu Sage-koodilla
MOD = 7347249713
MY_RING = Integers(MOD)
ZERO = MY_RING(0)
ONE = MY_RING(1)
TWO = MY_RING(2)
pMemo = {}
def p(m, j, n):
if n==0: return ONE if j==0 else ZERO
memoKey = (m,j,n)
if memoKey in pMemo:
return pMemo[memoKey]
ret = ZERO
if j>0: ret += p(m, j-1, n-1)
if j<m-1: ret += p(m, j+1, n-1)
pMemo[memoKey] = ret
return ret
brwMemo = {}
def brwRec(m, r, n):
if n==0: return ONE
memoKey = (m,r,n)
if memoKey in brwMemo:
return brwMemo[memoKey]
ret = ZERO
if r>0:
for j in range(n):
ret += (p(m, 0, j) + p(m, m-1, j)) * brwRec(m+1, r-1, n-1-j)
ret += sum(p(m, j, n) for j in range(m)) #stay on the first path
brwMemo[memoKey] = ret
return ret
def brwMatrixMethod(r, n):
bds = [(i,j) for i in range(-r, 1) for j in range(r+1) if j-i<=r]
states = []
for (i,j) in bds:
states += [(i,j,k) for k in range(i, j+1)]
matN = len(states)
startState = states.index((0,0,0))
arr = [[0]*matN for _ in range(matN)]
for ind1, (i,j,k) in enumerate(states):
i2 = i-1 if k-1<i else i
if j-i2<=r:
s2 = (i2, j, k-1)
arr[ind1][states.index(s2)] += 1
j2 = j+1 if k+1>j else j
if j2-i<=r:
s2 = (i, j2, k+1)
arr[ind1][states.index(s2)] += 1
return sum( (Matrix(MY_RING, arr)**n)[startState])
def brw(r, n):
if r==2: return (TWO**((n+3)/2) if n%2 else 3*TWO**(n/2)) - 2
#if r==3: TODO
if r<5: return brwMatrixMethod(r, n)
return brwRec(1, r, n)
testCases = [
(1, 3),
(2, 3),
(2, 4),
(2, 7),
(3, 5),
(3, 12),
(4, 15),
(5, 16),
(2, 20),
(2, 109),
(3, 67),
(3, 174),
(11, 23),
(12, 43),
(15, 38),
(21, 45),
(3, 923),
(2, 242398),
(4, 923847),
(2, 12345678910),
]
for (r, n) in testCases:
print (r, n, brw(r,n))
Yllä, arvolle (kakkosen potensseja sisältävä) kaava toimi myös kun laskenta tehtiin modulossa. Pohdi kuinka se onnistuisi muille kaavoille (ts. jos potenssiin korotettava luku ei ole kokonaisluku).
Perustele tapauksen kaava . Vinkki: Käytä edellä ollutta rekursiota. Huomaa, että 3-putken kävelyt ovat muotoa askel keskelle ja takaisin jompaan kumpaan päätyyn jne. Käytä geometrisen summan kaavaa.
Olkoon pisteet , ja . Määritellään funktio tulo pisteen etäisyyksistä pisteisiin ja .
Funktion f tasa-arvo käyrä {f=1} sivuaa ympyrää pisteessä D.
Selvitä parametrin arvo, jolla yo. kuvan tilanne toteutuu eli :n tasa-arvo käyrä sivuaa yksikköympyrää oikeassa puolitasossa.
Ratkaisu
Kompleksitulkinnat
Tulkitaan taso kompleksitasoksi, jolloin , ja . Tutkitaan funktiota . (Huom: etäisyyden neliö on 1 joss etäisyys on 1.) Jotta sivuaminen tapahtuu pisteessä , täytyy arvon olla funktion lokaali maksimi ja sekä . Selvitetään ensin funktion lauseke:
Merkitään . Nyt voidaan derivoida
Koska , on , joka sijoitettuna yhtälöön antaa
jonka ratkaisuna saadaan ja näin ollen .
Siis ja .
Tehtäviä
Yleistys useammalle pisteelle. Kuinka parametri-pisteet täytyy valita? Tasavälein valinta ei toimi, kuten ao. kuvasta neljälle pisteelle nähdään.
Osoita, että valittiinpa kuinka monta pistettä tahansa (äärellisen monta kuitenkin) ja asetettiinpa ne kuinka tahansa, niin ympyrän kehältä löytyy aina piste, jossa . (Määritelmässä otetaan tietenkin tulo kaikkien parametri-pisteiden yli.)
Kuinka suuri osa ympyrän kehästä voi korkeintaan olla joukon sisällä ja millä parametri-pisteiden ( kpl) asettelulla tämä saavutetaan? Onko kolmelle pisteelle alkuperäisessä tehtävässä ratkaistu tapaus optimaalinen?
Lasketaan ensin rationaaliluvulle . Oletetaan, että ja . Käytetään apuna kompleksista integrointia ja määritellään joukossa funktio
Selvitetään :n navat. Niitä ovat pisteet joille . On oltava , joten jollekin reaaliselle . Jotta , on oltava eli yhtäpitävästi . Näin ollen navat ovat , .
Positiivisella reaaliakselilla funktio on juuri haluttu integrandi. Lisäksi huomataan, että puolisuoralla , pätee . Nämä huomiot johtavat siihen että integrointi poluksi kannattaa valita (ks. Huomioita-osion tarkennus):
Polku koostuuu kolmesta osasta: jana origosta :ään, ympyrän kaari :stä :ään, josta lopuksi jana takaisin origoon. Interaktiivinen versio: https://www.desmos.com/calculator/nwrl7z0311
Navoista yksi, , on polun sisällä ja polku kiertää sen kerran vastapäivään. Näin ollen Residylauseen mukaan
Koska kaarella , jossa , pätee , ja kaaren pituus on korkeintaan , niin , kun , sillä .
Kuten tuli jo mainittua, .
Tehdään merkintöjä helpottava huomio: Nyt, koska polun vastapolulle derivaatta on vakio , saamme aiemman huomion, että tuottaa halutun integrandin puolisuoralla , nojalla että
Siis . Nyt tarvitsee enää laskea . Tätä varten kirjoitetaan muodossa
Nyt l’Hopitalin säännön nojalla ( ei ole :ssa, joten voidaan solvetaa myös kompleksifunktioille)
Siispä . Olemme täten ratkaisseet
Yleinen tapaus
Tapauksen ratkaisun muoto antaa vihjeen, että kaava pätisi myös kaikilla reaaliluvuilla . Tämä pitääkin paikkansa. Tehdään tätä varten itse asiassa vielä yleisemmin kaikille kompleksiluvuille , kun (olkoon tämä joukko ), määritelmä
Merkitään . Määritelmä on järkevä, sillä integraali suppenee: , kun on suuri. Lisäksi on holomorfinen :ssa kaikilla , sillä , kun .
Nyt kaikille kompakteille löytyy , siten että kaikilla . Näin ollen ja Fubini-Tonellin lauseen mukaan integrointijärjestyksen voi vaihtaa. Sovelletaan tätä suljetulle suoristuvalle käyrälle , jolloin
Siis on holomorfinen funktio. Mutta osoitimme rationaaliluku-tapauksessa, että , kun . Koska on myös :ssa holomorfinen (ks. Huomiot) ja :lla on :ssa kasautumispiste, niin Identtisyyslauseen mukaan funktiot ovat yhtäsuuret kaikilla , erityisesti kaikilla reaaliluvuilla .
Huomioita
Ei-kokonaisluku eksponentti on hieman ongelmallinen origossa, koska haaroittuminen. Tästä selvitään, kun rajoitetaan integraali ensin alhaalta ja otetaan sitten raja-arvo. Polussa tehdään origon lähellä pieni -kaari.
Kaikki funktion juuret ovat reaalisia: .
Edellisen perusteella ratkaisun kaavassa nimittäjässä oleva on holomorfinen :ssa (kun , niin , jossa sinillä ei ole juurta).
Koska on tiheä, niin yleinen tapaus seuraa jo pelkästä :n jatkuvuudesta. Mutta yo. tavalla tulos saatiin osoitettua kaikille kompleksiluvuille puolitasossa .
Tehtäviä
Laske integraali , kun . Vinkki: tee sopiva muuttujanvaihdos, joka johtaa ratkaistuun tapaukseen.
Sanan osajono on sanasta järjestyksessä poimituista merkeistä muodostettu uusi sana. Esimerkiksi sanalla ’ABBA’ on osajonot (11 kpl) [”, ’A’, ’AA’, ’AB’, ’ABA’, ’ABB’, ’ABBA’, ’B’, ’BA’, ’BB’, ’BBA’]. Huomaa, että esim. ’AB’ saadaan kahdella eri tavalla, mutta lasketaan vain kerran.
Kuinka laskea sanan osajonojen lukumäärä ? Oletetaan että :ssä esiintyvien merkkien lukumäärä on jokin ennalta kiinnitetty pienehkö luku. Halutaan löytää lineaariaikainen algoritmi.
Ratkaisu
Olkoon sana, jonka osajonot halutaan laskea. Olkoon :n pituus ja siinä esiintyvät merkit . Kun samaistetaan jokainen :n osajono siihen indeksien osajonoon, josta löytyy kaikkein aikaisimmin, vastaa jokainen sanan osajono solmusta lähtevää kävelyä suunnatulla graafilla , jonka solmut ovat ja solmusta lähtee kaaret missä on indeksi, jossa merkki esiintyy :ssä seuraavan kerran lukien indeksistä lähtien (ja jos ei enää esiinny, niin kaarta ei ole) ja tämä vastaavuus on bijektiivinen.
Esimerkiksi sanalle w = ”ABBA” graafi näyttää seuraavalta
Sanan ”ABBA” graafi. theFolls on dicti, jonka ao Python-koodi antaa, koodissa indeksit ovat siirretty yhdellä ja itse asiassa theFolls antaa seuraavan etsimisen alkaa itse siitä indeksistä missä ollaan.
Olkoon edellä kuvatun suunnatun graafin vierekkäisyysmatriisi. Osajonojen lukumäärä on tällöin ensimmäisen rivin summa seuraavasta matriisista
Huomaa, että edellä summa on itse asiassa vain äärellinen, sillä kaaret vievät aina eteen päin, joten graafin kävely voi olla korkeintaan :n pituinen ja , joten summan suppenemisen eikä :n kääntyvyyden kanssa tule ongelmia.
Merkitään . Mehän tarvitsemme vain sen ensimmäisen rivin summan, joten koko matriisia ei tarvitse muodostaa, eikä summaa laskea, vaan riittää ratkaista
,
missä on sarakevektori, jonka ensimmäinen komponentti on 1 ja loput nollia. Kun tämä kirjoitetaan Gauss-Jordanin menetelmän vaatimaan muotoon (jossa ei ratkaista koko käänteismatriisia, vain sen ensimmäinen rivi), niin huomataan, että ratkaiseminen on erittäin helppoa, sillä on alakolmiomatriisi, jossa on ykkösiä diagonaalilla ja jokaisessa sarakkeessa korkeintaan :ssä paikassa (ja muuten nollia). Näin ollen saadaan seuraava lineaariaikainen algoritmi (Python):
def theFolls(w):
"""map c -> list a where a[j] is the next index of c in w
when start searching from the index j"""
alph = list(set(c for c in w))
n = len(w)
ret = {c: [n]*n for c in alph}
prevs = {c: -1 for c in alph}
for i, c in enumerate(w):
for j in range(prevs[c]+1, i+1):
ret[c][j] = i
prevs[c] = i
return ret
def countSubsequences(w):
"""How many words can be obtained from w as subsequences"""
n = len(w)
folls = theFolls(w)
edges = [
[folls[c][i]+1 for c in folls if folls[c][i]<n]
for i in range(n)
]
ret = 0
a = [0 for _ in range(n+1)]
a[0] = 1
for i in range(n):
for j in edges[i]:
a[j] += a[i]
return sum(a)
class FenwickTree:
def __init__(self, n):
self.n = n+1
self.arr = [0]*self.n
def incr(self, ind, val):
"""Increment the value at index ind by val"""
if ind<0: raise Exception("Index out of bounds")
curr = ind+1
while curr<self.n:
self.arr[curr] += val
curr += curr&(-curr)
def getSum(self, upto):
"""Get the prefix sum up to index upto"""
s = 0
ind = upto+1
while ind>0:
s += self.arr[ind]
ind -= ind&(-ind)
return s
Tiedosto junarata6.py
from fenwickTree import FenwickTree
class IntvalSet:
"""Set where the included natural numbers
are given as intervals [a, b]"""
def __init__(self):
self.intvals = []
def has(self, x):
for (a,b) in self.intvals:
if a<=x and x<=b: return True
return False
def add(self, a, b):
self.intvals.append((a, b))
def getAll(self, lowerBdd=0):
"""generates all numbers in the set.
If lowerBdd is given,
generates only numbers >= lowerBdd"""
for (a,b) in self.intvals:
for x in range(max(a, lowerBdd), b+1):
yield x
def __str__(self):
return "[]" if len(self.intvals)==0 else " U ".join(["[%d, %d]" %x for x in self.intvals])
def getData(num):
ret = []
with open("junar"+str(num)+".in") as f:
ret = f.read().split("\n")
return [int(x) for x in ret[1:] if len(x)>0]
def getPoss(p):
"""Get list of positions of elements in the
permutation p of [1..n]
e.g. getPoss([2,5,3,1,4]) = [None, 3, 0, 2, 4, 1]
"""
ret = [None]*(len(p)+1)
for i,x in enumerate(p):
ret[x] = i
return ret
"""
Let's denote types of orderings of k-1, k and k+1
(or k and k+1; k-1 and k in cases k=1; k=n like this:
123: 0
132: 1
213: 2
231: 3
312: 4
321: 5
_12: 6
_21: 7
12_: 8
21_: 9
"""
def getTyyppi(a, b, c):
if a<b:
if b<c: return 0
if a<c: return 1
return 3
else: #b<a
if b>c: return 5
if a<c: return 2
return 4
def getNumOfRounds(poss):
"""How many rounds does it take to gather the numbers.
@param poss: index-positions of the permutation
"""
curr = 1
prevInd = poss[1]
r = 1
for curr in range(1, len(poss)):
currInd = poss[curr]
if currInd<prevInd: r += 1
prevInd = currInd
return r
def countScore2Pairs(arr):
n = len(arr)
poss = getPoss(arr)
toConsider = []
t = None
tempSet = None
for i, x in enumerate(arr):
if x==1: t = 6 if poss[x+1] > i else 7
elif x==n: t = 8 if poss[x-1] < i else 9
else: t = getTyyppi(poss[x-1], i, poss[x+1])
tempSet = IntvalSet()
if t==2:
tempSet.add(poss[x-1], poss[x+1]-1)
elif t==5:
tempSet.add(0, poss[x+1])
tempSet.add(poss[x-1], n-1)
elif t==1:
tempSet.add(poss[x-1]+1, poss[x+1])
elif t==7:
tempSet.add(0, poss[x+1])
elif t==9:
tempSet.add(poss[x-1], n-1)
toConsider.append(tempSet)
"""
mat = [[1 if toConsider[i].has(j) else 0 for j in range(n)]
for i in range(n)]
print ("To Consider Matrix:")
#print (mat)
for row in mat: print row
"""
tree = FenwickTree(n)
events = [[] for _ in range(n+1)] #grouped by time
#print ("Events len is "+str(len(events)))
for i, c in enumerate(toConsider):
for (j1, j2) in c.intvals:
#print ("Pushing events for interval [%d, %d]" %(j1, j2))
events[j1].append([j1, i, 1])
events[j2+1].append([j2+1, i, -1])
numPairs = 0
for k, evs in enumerate(events[:-1]):
for (j, i, d) in evs:
tree.incr(i, d)
for (j1, j2) in toConsider[k].intvals:
numPairs += tree.getSum(j2) - tree.getSum(j1-1)
#subtract the adjacent ones
x = arr[k]
if (x>1 and toConsider[k].has(poss[x-1])
and toConsider[poss[x-1]].has(k) ): numPairs -= 1
if (x<n and toConsider[k].has(poss[x+1])
and toConsider[poss[x+1]].has(k) ): numPairs -= 1
return numPairs/2
def junarata(arr):
n = len(arr)
poss = getPoss(arr)
origRounds = getNumOfRounds(poss)
numPairs = countScore2Pairs(arr)
return "%d %d %d" %(n, origRounds-2, numPairs)
import random
#a = range(1, 10); random.shuffle(a); print(junarata(a));
#Solve putkaposti
for i in range(1, 10):
print(junarata(getData(i)))
#Test execution time
"""
import time
startT = time.time()
print (junarata(getData(9)))
print ("Time: "+str(time.time()-startT))
"""
class IntvalSet:
"""Set where the included natural numbers
are given as intervals [a, b]"""
def __init__(self):
self.intvals = []
def has(self, x):
for (a,b) in self.intvals:
if a<=x and x<=b: return True
return False
def add(self, a, b):
self.intvals.append((a, b))
def getAll(self, lowerBdd=0):
"""generates all numbers in the set.
If lowerBdd is given,
generates only numbers >= lowerBdd"""
for (a,b) in self.intvals:
for x in range(max(a, lowerBdd), b+1):
yield x
def __str__(self):
return ", ".join([str(x) for x in self.getAll()])
def getData(num):
ret = []
with open("junar"+str(num)+".in") as f:
ret = f.read().split("\n")
return [int(x) for x in ret[1:] if len(x)>0]
def getPoss(p):
"""Get list of positions of elements in the
permutation p of [1..n]
e.g. getPoss([2,5,3,1,4]) = [None, 3, 0, 2, 4, 1]
"""
ret = [None]*(len(p)+1)
for i,x in enumerate(p):
ret[x] = i
return ret
"""
Let's denote types of orderings of k-1, k and k+1
(or k and k+1; k-1 and k in cases k=1; k=n like this:
123: 0
132: 1
213: 2
231: 3
312: 4
321: 5
_12: 6
_21: 7
12_: 8
21_: 9
"""
def getTyyppi(a, b, c):
if a<b:
if b<c: return 0
if a<c: return 1
return 3
else: #b<a
if b>c: return 5
if a<c: return 2
return 4
def getNumOfRounds(poss):
"""How many rounds does it take to gather the numbers.
@param poss: index-positions of the permutation
"""
curr = 1
prevInd = poss[1]
r = 1
for curr in range(1, len(poss)):
currInd = poss[curr]
if currInd<prevInd: r += 1
prevInd = currInd
return r
"""
As the above algorithm shows, the number of rounds for a permutation is
1 + #{k | poss[k+1]<poss[k]}
A swap can decrease the rounds by at most 2.
This can be seen from the following:
Let's consider for each k in [1..n] the positions that into which it is
moved, decrease the rounds. This can only ever be at most 1.
But a swap moves two, so if the other one also is a decreasing move,
we get 2, except in the case when we swap some k and k+1,
since that only decreases the rounds by 1.
(Look at the types, and consider for each how it decreases rounds.)
Let's hope the answer will always be 'score 2' (decreases rounds by 2) as
the existence of such swap is highly probably when we have a large random
permutation.
"""
def countScore2Pairs(arr):
n = len(arr)
poss = getPoss(arr)
toConsider = []
t = None
tempSet = None
for i, x in enumerate(arr):
if x==1: t = 6 if poss[x+1] > i else 7
elif x==n: t = 8 if poss[x-1] < i else 9
else: t = getTyyppi(poss[x-1], i, poss[x+1])
tempSet = IntvalSet()
if t==2:
tempSet.add(poss[x-1], poss[x+1]-1)
elif t==5:
tempSet.add(0, poss[x+1])
tempSet.add(poss[x-1], n-1)
elif t==1:
tempSet.add(poss[x-1]+1, poss[x+1])
elif t==7:
tempSet.add(0, poss[x+1])
elif t==9:
tempSet.add(poss[x-1], n-1)
toConsider.append(tempSet)
numPairs = 0
for i, x in enumerate(arr):
#if i%1000==0: print ("i="+str(i))
for j in toConsider[i].getAll(i+1):
if toConsider[j].has(i) and abs(x-arr[j])>1:
numPairs += 1
return numPairs
def junarata(arr):
n = len(arr)
poss = getPoss(arr)
origRounds = getNumOfRounds(poss)
numPairs = countScore2Pairs(arr)
return "%d %d %d" %(n, origRounds-2, numPairs)
##---------------------------------------------------
## Random tests
import random
def randomJuna(n):
a = range(1, n+1)
random.shuffle(a)
return [int(x) for x in junarata(a).split()]
from collections import Counter
def genJunaData(n, simuN):
d = [randomJuna(n) for _ in range(simuN)]
#print ([x[2] for x in d])
c1 = Counter([x[1] for x in d])
c2 = Counter([x[2] for x in d])
c = c2
return [ [k, c[k]] for k in sorted(c.keys()) ]
##-------------------------------------------------------
#Solve putkaposti
for i in range(1, 10):
print(junarata(getData(i)))
""" Test execution time
import time
startT = time.time()
print (junarata(getData(9)))
print ("Time: "+str(time.time()-startT))
"""
Pelissä on kaksi kolikkoa, kutsutaan niitä ensimmäinen ja toinen kolikko. Todennäköisyydet että näillä tulee kruuna (H) ovat vastaavasti ja .
Peli etenee seuraavasti: Pelaaja heittää kolikkoja toistuvasti ja pistää merkille minkä tuloksen hän sai (HT, TH, HH tai TT). Jos hän jossain vaiheessa saa kaksi kertaa putkeen tuloksen HH tai kaksi kertaa putkeen tuloksen TT, niin peli päättyy. Jos taas hänen viime tuloksensa on HH ja hän saa heti tämän jälkeen tuloksen TT, niin hän pääsee bonuspeliin. Bonuspelissä hän heittää molempia kolikkoja (erikseen) kaksi kertaa. Jos ensimmäisellä kolikolla tulee ensimmäisellä heitolla H, pelaaja voittaa 5 pistettä, mutta jos tämän jälkeen toisella heitolla tulee H, pelaaja menettää 3 pistettä (eli voittaa vain 2 pistettä yhteensä). Mikäli ensimmäisellä heitolla tulee T, pelaaja ei voita eikä häviä mitään. Vastaavat luvut toiselle kolikolle ovat 3 ja -4 eli HT antaa 3 pistettä ja HH 3-4 = -1 pistettä. Bonuspelin jälkeen peli jatkuu normaalisti (bonuspelin heittoja ei rekisteröidä, eli pelissä viimeksi on tullut TT).
Miten vääntyneet kolikot peliin kannattaa ottaa (eli kuinka muuttujat ja väliltä valita), kun halutaan maksimoida pelaajan voittojen odotusarvo?
Ratkaisu
Lasketaan todennäköisyydet
Muodostetaan Markovin ketju tiloina ”HT tai TH”, ”HH”, ”TT” ja ”End”.
Pelin Markovin ketju, sen siirtymätodennäköisyysmatriisi ja fundamentaalimatriisi. Olemme kiinnostuneet fundamentaalimatriisin ensimmäisen rivin toisesta alkiosta, joka kertoo odotusarvon kuinka monta kertaa ketju käy tilassa HH ennen absorboitumista, kun se alkaa tilasta ”HT tai TH”. (Aloitetaan tästä tilasta, koska silloin ei ole väliä mitä on tullut viimeksi.)
Siirtymätodennäköisyydet ovat muuten tilasta riippumattomat, paitsi tiloista ”HH” ja ”TT” ei mennäkkään vastaavilla heitoilla niihin itseihinsä vaan tilaan ”End”. Tila ”End” on absorboiva.
Pelin voiton odotusarvo saadaan (iteroidun odotusarvon kaavan nojalla) kertomalla yhden bonuspelin voiton odotusarvo () sillä kuinka monta kertaa siihen odotusarvoisesti päästään. Tämä odotusarvo taas saadaan kertomalla tilassa ”HH” vierailujen odotusarvo siirtymätodennäköisyydellä ”HH” -> ”TT”, joka on . Näin ollen pelin voiton odotusarvo on
Kuvasta nähdään, että tämän maksimikohta joukossa on jossain :n kohdilla:
Odotusarvo riippuu muuttujista ja . Kuvaaja ja tasa-arvo käyrä-kuvaaja.
Tässä javascript-funktiot odotusarvon ja sen gradientin laskemiseksi:
/*
(x(5x - 3x^2 + 3y-4y^2)y(1 - x - y + xy)(2 - x - y + xy))
/
(1 - xy(1 - x - y + xy)
- (x + y - 2xy) (1 + xy) (2 - x - y + xy))
, x\in[0,1], y\in [0,1]
*/
const f = (x, y) => {
let g = 5*x - 3*x**2 + 3*y-4*y**2;
let num = g*x*y*(1 - x - y + x*y)*(2 - x - y + x*y);
let denom = 1 - x*y*(1 - x - y + x*y)
- (x + y - 2*x*y)*(1 + x*y)*(2 - x - y + x*y);
return num/denom;
};
const gradF = (x, y) => {
let p1 = 5*x - 3*x**2 + 3*y-4*y**2;
let p2 = x*y*(1-x-y+x*y); //= xy - x^2y - xy^2 + x^2y^2
let p3 = 2 - x - y + x*y;
let num = p1*p2*p3;
let denom = 1 - x*y*(1 - x - y + x*y)
- (x + y - 2*x*y)*(1 + x*y)*(2 - x - y + x*y);
let dxp1 = 5 - 6*x;
let dxp2 = y - 2*x*y - y**2 + 2*x*y**2;
let dxp3 = -1+y;
let dxnum = dxp1*p2*p3 + p1*dxp2*p3 + p1*p2*dxp3;
let dxdenom = (y-2)*(y-1)**2 + 3*x**2*y*(1-3*y+2*y**2)
+ x*(2-8*y+14*y**2-6*y**3);
let dyp1 = 3-8*y;
let dyp2 = x - x**2 - 2*x*y + 2*x**2*y;
let dyp3 = -1 + x;
let dynum = dyp1*p2*p3 + p1*dyp2*p3 + p1*p2*dyp3;
let dydenom = 2*(y-1) + x**2*(-4+14*y-9*y**2)
+ x*(5-8*y+3*y**2) + x**3*(1-6*y+6*y**2)
return [
(dxnum*denom-num*dxdenom)/denom**2,
(dynum*denom-num*dydenom)/denom**2
];
};
Jyrkimmän nousun menetelmällä (gradient descent) tai muuten löydetään maksimi