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?
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
Pelaaja saa aluksi yhden nopan. Joka kierros pelaaja heittää kaikki noppansa ja menettää ne joiden osoittamaa arvoa tuli useampi kuin yksi. Kierroksen päätteeksi pelaaja saa aina yhden uuden nopan. Tavoite on saada seitsemän noppaa (tai yleisemmin noppaa, kun nopassa on tahkoa). Toisin sanoen peli päättyy, kun pelaajalla on kuusi noppaa ja niillä tulee jokaisella eri arvo. Mikä on yhden pelin kierrosten määrän () odotusarvo?
Ratkaisu
Peli on Markovin ketju, jossa tilat ovat noppien määrät . Meidän täytyy selvittää siirtymätodennäköisyydet näiden välillä. Pienille arvoille, kuten , tämä voidaan tehdä tutkimalla kaikki mahdolliset heitot ja kirjaamalla kuinka moni niistä säilyttää kuinkakin monta noppaa. Seuraava Sage-koodi tekee tämän:
from itertools import product
from collections import Counter
def getTransitionsProbsBrute1(diceAmounts, dieFaces):
n = dieFaces+1
ret = [0]*n
for dice in product(range(dieFaces), repeat=diceAmounts):
keep = sum(x for x in Counter(dice).values() if x==1)
#we're adding 1 die after round, but 1 is the
#first state, so keep is the correct index
ret[keep] += 1
tot = dieFaces**diceAmounts
return [x/tot for x in ret]
Laskentaa voidaan hieman nopeuttaa käyttämällä kombinaatioita (takaisin panolla):
from itertools import combinations_with_replacement as cmb_rep
from collections import Counter
def getType(dice):
return list(Counter(dice).values())
def getTransitionsProbsBrute2(diceAmount, dieFaces):
n = dieFaces+1
ret = [0]*n
for dice in cmb_rep(range(dieFaces), r=diceAmount):
keep = sum(x for x in Counter(dice).values() if x==1)
#multinomial of the type gives the amount
#of orderings this corresponds to when account for all
weight = multinomial(getType(dice))
ret[keep] += weight
tot = dieFaces**diceAmount
return [x/tot for x in ret]
Tässä tarvittiin tietää kombinaatiosta sen tyyppi eli siinä esiintyvien lukujen esiintymien lukumäärät (koottuna vektoriksi).
Nyt voidaan muodostaa siirtymämatriisi ja käyttää tavanomaisia keinoja:n selvittämiseen:
def getTransitionMatrixBrute(dieFaces):
arr = [getTransitionsProbsBrute2(i, dieFaces)
for i in range(1, dieFaces+2)]
return Matrix(arr)
def getFundamentalMatrix(pMat):
n = pMat.dimensions()[0]-1
arr = [[(1 if i==j else 0)-pMat[i][j]
for j in range(n)]
for i in range(n)]
return Matrix(arr)**-1
def getExpectedGameLen(dieFaces):
P = getTransitionMatrixBrute(dieFaces)
fundMat = getFundamentalMatrix(P)
return sum(fundMat[0])
print (getExpectedGameLen(6))
Ja näin saadaan tulos
Kombinaatioiden (eritoteen takaisinpanon kanssa) määrä kuitenkin kasvaa melko nopeasti, kun kasvaa. Edellä jo mainittua tyyppi-ideaa voidaan jatkojalostaa: se kuinka monta noppaa heittotuloksesta menetetään riippuu vain tyypistä (kaikki, joiden arvoa yli yksi kappale menetetään). Tyypit ovat noppien lukumäärän ositukset (paitsi viimeisen tilan ositus ei kelpaa, sillä se tarkoittaa eri arvoa, mutta nopassa on vain eri arvoa. (Tästä vielä huomio-osioissa.) Nyt täytyy enää laskea kuinka moni noppien heitto vastaa kutakin ositusta. Tätä on selvitetty koodin kommenteissa:
def getFactorOfType(t, dieFaces):
#first choose a value for each number in the type
ret = factorial(dieFaces)/factorial(dieFaces-len(t))
#divide away the orderings in the type
#(we need the type's type)
ret /= factorialProd(getType(t))
#now we can multiply by the total number of orderings
ret *= multinomial(t)
return ret
def getTransitionMatrix(dieFaces):
n = dieFaces+1
arr = [[0]*n for i in range(n)]
for i in range(1, n+1):
for t in Partitions(i):
if (len(t)>dieFaces): continue
toState = i-sum(x for x in t if x>1)
weight = getFactorOfType(t, dieFaces)
arr[i-1][toState] += weight
for i in range(n):
tot = dieFaces**(i+1)
for j in range(n):
arr[i][j] /= tot
return Matrix(arr)
Tässä vielä lopullinen tyypeillä laskeva koodi kokonaisuudessaan ja taulukko tuloksesta muutamilla :n arvoilla:
from collections import Counter
def getType(dice):
return list(Counter(dice).values())
def factorialProd(arr):
return reduce(lambda acc,x: acc*factorial(x), arr, 1)
def getFactorOfType(t, dieFaces):
ret = factorial(dieFaces)/factorial(dieFaces-len(t))
ret /= factorialProd(getType(t))
ret *= multinomial(t)
return ret
def getTransitionMatrix(dieFaces):
n = dieFaces+1
arr = [[0]*n for i in range(n)]
for i in range(1, n+1):
for t in Partitions(i):
if (len(t)>dieFaces): continue
toState = i-sum(x for x in t if x>1)
weight = getFactorOfType(t, dieFaces)
arr[i-1][toState] += weight
for i in range(n):
tot = dieFaces**(i+1)
for j in range(n):
arr[i][j] /= tot
return Matrix(arr)
def getFundamentalMatrix(pMat):
n = pMat.dimensions()[0]-1
arr = [[(1 if i==j else 0)-pMat[i][j]
for j in range(n)]
for i in range(n)]
return Matrix(arr)**-1
def getExpectedGameLen(dieFaces):
P = getTransitionMatrix(dieFaces)
fundMat = getFundamentalMatrix(P)
return sum(fundMat[0])
res = [[d, float(getExpectedGameLen(Integer(d)))]
for d in range(1, 21)]
print (res)
d
E[N]
1
1
2
4
3
15
4
80.96296
5
718.61364
6
10655.34888
7
261903.04770
8
10636087.48690
9
713183947.85983
10
7.89467e+10
11
1.44259e+13
12
4.35102e+15
13
2.16594e+18
14
1.77942e+21
15
2.41246e+24
16
5.39710e+27
17
1.99232e+31
18
1.21348e+35
19
1.21944e+39
20
2.02174e+43
Huomioita
Viimeisen tilan siirtymiä ei tarvitsisi laskea (siinä mielessä, että peli jatkuisi) vaan se asetetaan absorboivaksi eikä sitä tarvita odotusarvon laskussa ollenkaan. Itse asiassa sen laskeminen on juuri kaikista matriisin riveistä vaativin, sillä tarvittavia objekteja (permutaatioita takaisinpanolla, kombinaatioita takaisin panolla tai osituksia) on eniten.
Myös ensimmäinen tila on käytännössä turha: yhtä noppaa on turha edes heittää, koska sitä ei voi menettää; ottaa vaan suoraan toisen nopan. Mutta pidetään se mukana yksinkertaisuuden vuoksi ja lasketaan pelin pituuteen mukaan myös nämä deterministiset askeleet.
Tehtäviä
Muokkaa koodia, niin että turhia viimeisen tilan siirtymiä ei lasketa. Aseta viimeiseen tilaan itsesiirtymä todennäköisyydellä 1 tai unohda koko viimeinen tila, sillä sitä ei laskuissa tarvita.
Entä jos sovitaan, että pelaaja häviää pelin, mikäli hän jollain kierroksella menettää kaikki noppansa. Mikä on häviämisen todennäköisyys?
Voidaan sopia mitä erilaisimpia sääntöjä siitä miten pelaaja saa lisänopan /-noppia tai menettää niitä riippuen hänen heitostaan. Tutki jotain tällaista muokattua peliä.
Kaikkihan tietävät, että ympyrä on paras muoto kun halutaan aidata tietyn suuruinen ala käyttäen mahdollisimman vähän aitaa (ks. isoperimetrinen epäyhtälö). Mutta entäpä, jos onkin tehtävä kaksi karsinaa, jotka voivat käyttää yhteistä aitaa? Entä yleisemmin karsinaa?
Oletetaan että yhden karsinan ala on ja yritetään löytää mahdollisimman pieni piirinen aitaus.
Ensimmäinen vaihtoehto, joka tulee mieleen on kaiketi ”tavallinen” suorakaiteen mallinen, jossa on yksi väliaita:
Optimoidaan piiri ja saadaan ja .
Mutta onko tämä paras mahdollinen? Koska ympyrä on paras ratkaisu yhdelle karsinalle, niin kokeillaanpa jakaa ympyrä kahtia:
Saadaan ja . Siis parempi kuin suorakaide-ratkaisu.
Mutta entäpä jos yhdistettäisiin hieman kumpaakin: laitetaan keskelle suorakaide ja päihin puoliympyrät (ja tietenkin keskelle jakoaita) ja optimoidaan tämä muoto:
Optimiksi saadaan ja . Löydettiin siis halkaistua ympyrääkin parempi ratkaisu.
Toinen tapa yrittää käyttää ympyrää olisi ollut tehdä kaksi täysin erillistä ympyräkarsinaa. Tällöin yhdellä on pienempi säde () mutta piiri on ja saadaan jo siitä (optimoimattomasta) ratkaisusta, että laitetaan kaksi neliötä vierekkäin.
Olemme tähän asti ajatelleet, että aitauksen kuuluisi olla konveksi (tietenkin kummankin karsinan pitää olla konveksi, mutta että koko rakennelman?!?!?). Eihän sen täydykään: otetaan vain kaksi sopivan kokoista ympyrää ja läimäytetään ne yhteen. Ei keskikohdasta, vaan optimoidaan tämä kohta.
Jos karsinat eivät olisikaan samankokoiset, niin tuplakupla ratkaisussa jakoaita ei olekaan jana, vaan ympyrän kaari (ks. tuplakuplan määritelmä), joten karsinatkaan eivät välttämättä ole konvekseja. Jakokaari määräytyy siitä, että kaarien täytyy kohdata toisensa 120 asteen kulmassa eli niiden tangentit jakavat täysikulman kolmeen yhtäsuureen osaan.
Tarkka ratkaisu, joka tästä 120 asteen vaatimuksesta nähdään on , ja
Jos tavallisesta 52 kortin korttipakasta nostetaan satunnainen 7 kortin käsi, mikä on todennäköisyys, että käsi sisältää (ainakin) peräkkäistä saman maan korttia?
Merkitään ”käsi sisältää kortit ”, arvoille ja ♠, ♥, ♣, ♦. Merkitään näiden kaikkien tapahtumien joukkoa :lla. Kysytty todennäköisyys on tällöin
Merkitään tapahtumalle symbolilla niiden korttien joukkoa, jotka siinä vaaditaan kädessä esiintymään. Huomataan, että tämä relaatio on siinä mielessä käänteinen, että kun tapahtumia leikataan, vastaa tämä joukkojen yhdistettä. (Leikkautapahtuma tapahtuu jos ja vain jos käsi sisältää kaikki vaaditut kortit.)
Todennäköisyys riippuu vain vaadittujen korttien unionin koosta sillä siinä vaaditaan leikkauksen korttien esiintyvän kädessä eikä sillä ole väliä mitkä kortit ne ovat. Hypergeometrisen jakauman mukaan tämä todennäköisyys on
Täytyy siis selvittää kuinka monta :n kokoista unionia tasolla on. Merkitään tätä .
Koska , kun , niin tarvitaan vain arvot, joissa . Lisäksi, koska tason unioni koostuu joukosta ja ensimmäinen tuo alkiota ja seuraavat jokainen ainakin yhden lisää (joukot ovat pötköjä, jotka menevät maksimissaan :n verran päällekkäin), niin saadaan myös yläraja .
Lukujen laskeminen voidaan tehdä brute-forcena. Tapaus on melko raskahko, sillä siinä täytyy laskea arvoon asti ja . Tapaus on kuitenkin toisella tapaa yksinkertaisempi ja sen voi ratkaista tutkimalla kuinka monella tapaa neljän 13-ketjun graafista voidaan valita aligraafi, joka on kokoelma ketjuja (nämä aliketjujen pituudet vastaavat sitä tyyppiä millainen yhdiste ’ista muodostuu). Tässä joukot vastaavat graafin sivuja ja se että joukko on yhdisteessä mukana vastaa sitä, että sivu on aligraafissa mukana.
Saadaan seuraavat tulokset:
r
P(väh. r pituinen värisuora)
approks
2
686898/1194505
0.575
3
1074209/16723070
0.0642
4
151/30940
0.00488
5
407/1454180
0.000280
6
361/33446140
0.0000108
7
1/4778020
2.093e-7
Javascript-koodi:
function binCoeff(n, k) {
if (k < 0 || k > n) return 0;
if (k === 0 || k === n) return 1;
let ret = 1;
for (let i=0; i<Math.min(k, n-k); i++){
ret = ret*(n-i)/(i+1);
}
return ret;
}
function* generCombinations(arr, r) {
const result = [];
result.length = r;
function* makeThem(len, start) {
if(len===0) {
yield result.slice();
} else {
for (var i=start; i<=arr.length-len; i++) {
result[result.length-len] = arr[i];
yield* makeThem(len-1, i+1);
}
}
}
yield* makeThem(r, 0);
}
function linkBruteGeneral(j, k, m, linkLen) {
let ret = {};
let runStarts = [];
for (let i=0; i<k; i++) {
runStarts = [...runStarts,
...new Array(m-(linkLen-1)).fill(null).map((_,z)=>[z, i])];
}
for (let comb of generCombinations(runStarts, j)) {
let cardSet = new Set();
for (let card of comb) {
for (let follow=0; follow<linkLen; follow++) {
cardSet.add((card[0]+follow)+","+card[1]);
}
}
let key = cardSet.size;
if (!(key in ret)) ret[key] = 0;
ret[key] += 1;
}
return ret;
}
function calcProb(ds) {
const calcPart = (j) => {
let s = 0;
let d = ds[j];
for (let k in d) {
if (k>7) continue;
s += d[k]*binCoeff(52-k, 7-k);
}
return s;
};
let ret = 0;
for (let i=1; i<=ds.length; i++) {
ret += (i%2?1:-1)*calcPart(i);
}
return ret;
}
function makeTheFormula(ds) {
const makePart = (j) => {
let f = "(";
let d = ds[j];
let isFirst = true;
for (let k in d) {
if (k>7) continue;
if (isFirst) {
isFirst = false;
} else {
f += "+";
}
f += d[k]+`*binomial(${52-k}, ${7-k})`;
}
return f+")";
};
let ret = "";
for (let i=1; i<=ds.length; i++) {
ret += (i===1?'':(i%2?'+':'-')) + makePart(i);
}
return ret;
}
let testK = 4;
let testM = 13;
let testR = 3;
let maxNeedJ = 7-testR+1;
let ds = [];
for (let j=0; j<=maxNeedJ; j++) {
ds.push(linkBruteGeneral(j, testK, testM, testR));
}
console.log(calcProb(ds));
console.log(makeTheFormula(ds));
Koodi toiselle tavalle tapauksessa r=2:
/** list of partitions of n to at most maxPartsN parts that are at most maxSize */
function getPartitions(n, maxPartsN, maxSize) {
if (n===0) return [[]];
let canMakeMax = maxPartsN*maxSize;
if (n>canMakeMax) return [];
if (n===canMakeMax) return [new Array(maxPartsN).fill(maxSize)];
let ret = [];
for (let x=Math.min(n, maxSize); x>=0; x--) {
let toConcat = getPartitions(n-x, maxPartsN-1, x).map(p=>[x].concat(p));
ret = ret.concat(toConcat);
}
return ret
}
function factorial(n) {
let ret = 1;
for (let i=2; i<=n; i++) ret *= i;
return ret;
}
function binCoeff(n, k) {
if (k < 0 || k > n) return 0;
if (k === 0 || k === n) return 1;
let ret = 1;
for (let i=0; i<Math.min(k, n-k); i++){
ret = ret*(n-i)/(i+1);
}
return ret;
}
/** In how many ways can subchains be fitted into chains.
* Chain length is the number of edges in it.
*/
const linkageMemo = {};
function countLinkages(chains, subchains) {
//debugger;
let memoKey = chains.join(",")+";"+subchains.join(",");
if (memoKey in linkageMemo) return linkageMemo[memoKey];
if (subchains.length===0) return 1;
let s = 0;
let currSub = subchains[0];
let restSubs = subchains.slice(1);
for (let cI=0; cI<chains.length; cI++) {
let cLen = chains[cI];
let maxInd = cLen-currSub;
for (let ind=0; ind<=maxInd; ind++) {
let restChains = chains.slice(0, cI).concat(chains.slice(cI+1));
if (ind>1) restChains.push(ind-1);
if (ind<maxInd-1) restChains.push(maxInd-ind-1);
restChains.sort((a,b)=>b-a);
s += countLinkages(restChains, restSubs);
}
}
linkageMemo[memoKey] = s;
return s;
}
function permuFactor(arr) {
let counts = {};
for (let x of arr) {
if (!(x in counts)) counts[x] = 0;
counts[x] += 1;
}
let ret = 1;
for (let val of Object.values(counts)) ret *= factorial(val);
return ret;
}
function countAllLinkages(j, k, m) {
let chains = new Array(k).fill(m-1);
let linksN = k*(m-1);
let partis = getPartitions(j, linksN, linksN);
let ret = {};
for (let parti of partis) {
let key = parti.reduce((a,b)=>a+b+1,0);
if (!(key in ret)) ret[key] = 0;
ret[key] += countLinkages(chains, parti)/permuFactor(parti);
}
return ret;
}
/* Given the amounts of linkages calculate the numerator of probability of having a 2-run
* ds = [
{
"0": 1
},
{
"2": 48
},
{
"3": 44,
"4": 1084
},
{
"4": 40,
"5": 1944,
"6": 15312
}, ...
]
*/
function calcProb(ds) {
const calcPart = (j) => {
let s = 0;
let d = ds[j];
for (let k in d) {
if (k>7) continue;
s += d[k]*binCoeff(52-k, 7-k);
}
return s;
};
let ret = 0;
for (let i=1; i<=6; i++) {
ret += (i%2?1:-1)*calcPart(i);
}
return ret;
}
function makeTheFormula(ds) {
const makePart = (j) => {
let f = "(";
let d = ds[j];
let isFirst = true;
for (let k in d) {
if (k>7) continue;
if (isFirst) {
isFirst = false;
} else {
f += "+";
}
f += d[k]+`*binomial(${52-k}, ${7-k})`;
}
return f+")";
};
let ret = "";
for (let i=1; i<=6; i++) {
ret += (i===1?'':(i%2?'+':'-')) + makePart(i);
}
return ret;
}
let testK = 4;
let testM = 13;
let ds = [];
for (let j=0; j<=6; j++) {
ds.push((countAllLinkages(j, testK, testM)));
}
console.log(makeTheFormula(ds));
console.log(calcProb(ds));
Olkoon tasossa toisiaan leikkaamatonta -säteistä ympyrää. (Ympyrällä tarkoitetaan sen kaarta.) Mikä on todennäköisyys, että kun valitaan näiltä piste satunnaisesti, tästä pisteestä ei näy muita ympyröitä?
Ympyrän 2 pisteestä näkyy kaikki muut ympyrät. Pisteestä taas ei näy mikään toinen ympyrä, sillä ne ovat kaikki :hen piirretyn tangenttisuoran ”väärällä” puolella.
Ja tämä siis aivan riippumatta ympyröiden sijainneista (paitsi eivät saa leikata toisiaan, kuten sovittiin).
Todistuksen runko
Ympyröiden konveksiverho . Kaariosuudet keltaisella ja janaosuudet ruskealla. Vihreät pisteet ympyröillä ovat niiden välisten tangenttijanojen päätepisteitä, ”kaikkein uloimmaiset” tangenttijanat ovat mukana :ssä.
Joukon reuna on janoista ja ympyränkaarista koostuva derivoituva Jordanin käyrä.
Ympyrän piste kuuluu :hen täsmälleen silloin, kun se on sellainen piste, josta ei näy mikään muu ympyrä. (Pois lukien äärellisen monta pistettä, jotka ovat :n janojen päätepisteitä: janat ovat ympyröiden välisiä tangentteja, joten niiden päätepisteet leikkaavat kahta ympyrää, joten niistä on näköyhteys. Näiden joukko on kuitenkin äärellisenä nollamittainen eikä vaikuta todennäköisyyteen.)
Toisaalta on käyrällä olevien ympyränkaarien yhteenlaskettu mitta, sillä janaosuudella kaarevuus ja kaariosuudella , sillä jokaisen ympyrän säde on (ks. kaarevuudesta Wikipediassa).
Siis suotuisan osan mitta on . Kaikkien ympyröiden mitta on , joten väite seuraa.
Jos ympyröiden säteet ovat eri suuruisia, ympyröiden paikat vaikuttavat tulokseen. Tämä johtuu siitä, yo. integraalissa kaarevuus ei ole joka ympyrällä vakio vaan tulos riippuu siitä millaisia ympyröitä ulkokaaressa on mukana minkäkin verran. Ks. esimerkkikuva alla.
Erisäteisille ympyröille voi käydä näin. Selvästi jälkimmäisessä tapauksessa suotuisaa mittaa on vähemmän kuin ensimmäisessä.
Kaikkihan tietätävät että graafin solmujen välillä olevien -kävelyjen lukumäärät saadaan korottamalla graafin vierekkäisyysmatriisi potenssiin . Mutta entä jos matriisissa käytetään kaarien painotuksia ja matriisitulossa rivin ja sarakkeen pistetulon paikalla alkioiden summien minimiä? Tämä on Hedetniemen matriisisumma.
Määritelmä
Matriisien (kokoa ) ja (kokoa ) Hedetniemen summa on -matriisi , jonka -alkio on
Huom. Sallimme matriisin alkioksi myös plus äärettömän . Määrittelemme ja kaikille luvuille ja myös tapauksessa .
Esimerkki
Hedetniemen summan alkion muodostuminen korostettuna. Matriisi on alla esiintyvän graafin kaarien painomatriisi.
Mihin tätä summaa sitten käytetään? Sen avulla voidaan selvittää lyhyimmät reitit kaikkien solmuparien välillä graafissa, jonka kaarille on annettu ei-negatiiviset painokertoimet (etäisyydet). Muodostetaan graafille kaarien painomatriisi , jossa alkio , on :n ja :n välisen kaaren paino tai , jos solmujen välillä ei ole kaarta. Diagonaalialkiot asetetaan nollaksi, sillä solmusta itseensä on nollan askeleen reitti, jonka pituus (=sen käyttämien kaarien painojen summa) on nolla. Oletamme että graafi on yksinkertainen eli sillä ei ole kaaria solmusta itseensä eikä useampia kaaria kahden solmun välillä. Tämä on luonnollinen oletus kun haluamme selvittää lyhyimpiä reittejä, sillä luuppia solmusta itseensä ei kannata reitissä ikinä käyttää ja useammasta kaaresta solmujen välillä kannattaa aina valita se, jolla on pienin paino.
Kaaret ajatellaan suunnaatuiksi, mutta suuntaamattomassa tapauksessa ajatellaan, että kaari on kumpaankin suuntaan olemassa (samalla painolla). Suuntaamattomassa tapauksessa matriisi on siis symmetrinen.
Suuntaamaton graafi ja sen kaarien painomatriisi . Ensimmäinen rivi ja sarake ovat indeksejä.
Lasketaan matriisin iteroituja Hedetniemen summia . Merkitsemme siis (tämä ei sotkeutune tavalliseen matriisituloon, jota ei tässä postauksessa esiinny). Iteraation alkio kertoo lyhyimmän :n askeleen reitin pituuden solmusta solmuun . Koska graafi on äärellinen, käy jostakin :n arvosta lähtien niin, että eli jono stabiloituu. Ylläolevalle esimerkille
Hedetniemen matriisisumma-iteraatiot edellä olleen graafin kaarien painomatriisille. Jono stabiloituu arvosta lähtien. Kuten voidaan todeta, eniten askelia vievä lyhyin reitti (solmujen ja välillä) on 6 askelta: 2-0-1-3-4-6-5 ja sen pituus on . Se käykin kaikki graafin solmut läpi, joten tämä on maksimitapaus.
Reitin selvitys
Lyhyimmän reitin pituus on luettavissa matriisin alkiosta, mutta kuinka saamme tiedon mikä tuo reitti on? Se voidaan selvittää solmu kerrallaan lähtemällä päätepisteestä ja ratkaisemalla mikä sitä täytyy reitillä edeltää. Otetaan esimerkiksi edellä olleen graafin solmut ja . Matriisista nähdään, että lyhyimmän reitin pituus on . Tutkitaan nyt päätepisteen eli solmun naapureita (). Se kummasta kutoseen on tultu, täytyy toteuttaa yhtälö, että lyhyin reitti nollasta siihen on . Koska solmusta tulee 2:n painoinen kaari ja lyhyin reitti nollasta solmuun on :n mukaan pituudeltaan 4, niin valitaan solmu . Jatketaan nyt solmusta ja etsitään sen edeltäjä. Näin jatketaan kunnes päästään alkupisteeseen ja reitti on selvitetty.
Reitin selvityksen kaksi ensimmäistä askelta. Loput menevät vastaavasti.
Huomioita
Jos matriisin jää äärettömyyksiä jonon stabiloiduttua, tarkoittaa se että graafi on epäyhtenäinen: jostain solmusta ei ole mitään reittiä johonkin toiseen solmuun.
Stabiloituminen vie korkeintaan askelta, sillä lyhyin reitti ei vieraile missään solmussa kuin korkeintaan kerran.
Pienempiä (ennen stabiloitumista) iterantteja voi käyttää askelten määrän ollessa rajoitettu, sillä ne tosiaan kertovat sen hetkisen lyhyimmän etäisyyden kun käytetään korkeintaan askelta. (Jos haluaisi käyttää tasan askelta sen voisi varmaan toteuttaa asettamalla diagonaalialkiot äärettömiksi, jolloin solmussa ei ole luvallista pysyä. ???)
INFTY = 2**56 #number larger than any other occuring one
def hedetniemiMatrixSum(a, b):
m = len(a)
n = len(b)
p = len(b[0])
return [[min(a[i][k] + b[k][j] for k in range(n))
for j in range(p)]
for i in range(m)]
def findStableHedetniemiSum(a):
n = len(a)
prev = a
b = a
counter = 0
while counter<n:
b = hedetniemiMatrixSum(b, a)
if b == prev: break
prev = b
return b
def findPath(start, end, minDMat, a):
"""
start: index of the start node
end: index of the target node
minDMat: the stabilized matrix
containing the minimum paths lenghts
a: the weighted adjacency matrix
"""
n = len(a)
path = [end]
dist = minDMat[start][end]
if dist>=INFTY: return None #no path exists
curr = end
def findPrev():
for i in range(n):
if i==curr: continue
if dist-a[i][curr] == minDMat[start][i]: return i
counter = 0
while curr != start and counter<n:
prev = findPrev()
dist -= a[prev][curr]
curr = prev
path.append(curr)
return path[::-1]
#the example matrix
a = [
[0,1,2,INFTY,INFTY,8,INFTY],
[1,0,4,2,INFTY,INFTY,INFTY],
[2,4,0,INFTY,7,INFTY,INFTY],
[INFTY,2,INFTY,0,1,INFTY,INFTY],
[INFTY,INFTY,7,1,0,INFTY,2],
[8,INFTY,INFTY,INFTY,INFTY,0,1],
[INFTY,INFTY,INFTY,INFTY,2,1,0]
]
b = findStableHedetniemiSum(a)
print b
print findPath(0, 6, b, a)
Kuinka monella tavalla voidaan -laudalle asettaa 4 ratsua, siten että mikään ei uhkaa toista? Kaikki ratsut ovat identtisiä ja vihollisia keskenään.
Ratkaisu:
Merkitään eli kaikkien laudan ruutujen joukko. Kaikki mahdolliset neljän ratsun asettelut ovat nyt joukko
Määritellään kaarijoukko kuvastamaan sitä mihin ruutuun kustakin ruudusta ratsu voi liikkua, eli mitä kaikkia ruutuja se uhkaa. (Huom. ratsuissa on se mukava juttu, että ne eivät välitä laudan muista nappuloista vaan voivat hypätä näiden yli.) Näin saadaan –ratsun graafi.
Ratsun liikkumismahdollisuudet ovat 1 ruutu vaakasuunnassa ja 2 ruutua pystysuunnassa, tai kääntäen.
Tehdään tässä välissä sellainen huomio, että ratsun graafilla ei ole kolmioita: mitkään kolme eri ratsua (kun ovat laudalla eri ruuduissa) eivät voi uhata kaikki toisiaan. Tämä johtuu siitä, että ratsu vaihtaa aina liikkuessaan väriä: se liikkuu yhteensä kolme askelta mustalta valkealle / valkealta mustalle. Kolmesta ratsusta vähintään kaksi on samalla värillä, joten ne eivät voi uhata toisiaan. Ratsun graafi on juuri tämän värin vaihtamisen takia kaksijakoinen (valkeat ja mustat ruudut).
Määritellään joukot . Tarvitsemme joukkoja ja , sillä kysytty lukumäärä on joukon koko. Indeksijoukko (huom. merkinnät 12,… eivät aiheuta sekaannusta, sillä luvut pysyvät alle kymmenen). Voimme laskea inkluusio-eksluusiolla:
missä leikkaus tyhjän joukon yli tarkoittaa koko joukkoa . Tehtävän ratkaisemiseksi meidän on siis laskettava kaikkien erilaisten leikkausten koot. Monet näistä ovat symmetrian nojalla samoja ja osa graafin kolmio-vapauden takia mahdottomia. Ne jakautuvat seuraaviin luokkiin:
Erilaiset leikkaustyypit. Solmut ovat indeksejä ja , kaari näiden välillä tarkoittaa että on leikkauksessa mukana. Viereen merkitty kuinka monta kutakin leikkaustyyppiä on (symmetria, kuvassa on aina vain esimerkki numerointi solmuille). Taso, eli kuinka monta joukkoa leikkauksessa on, merkitty alle. Huomaa että tason kaikkinainen lukumäärä on : leikkaukseen valitaan joukkoa kaikista kuudesta. Kolmion sisältävät ovat mahdottomia eli niitä vastaavat leikkaukset ovat tyhjiä.
Merkitään
Siis kaaret (ja kaksi muuta saa olla mitä vain muita solmuja), 2-ketjut (ja viimeinen saa olla mikä vain muu solmu), erilliset kaaret, kaikki yhden naapureita, 4-ketjut ja 4-syklit.
Tällöin kysytty lukumäärä on
Tässä on sijoitettu ja jakaja tulee siitä, että ratsujen järjestyksellä ei ole kysymyksessä väliä, mutta me laskimme järjestettyjä nelikoita.
Tällaisten konfiguraatioiden laskemista graafilla käsittelimme viime kertojen postauksissa: Erillisten kaariparien laskeminen ja Graafin naapuruus-polynomeja. Meillä oli apuna graafin naapuruus-polynomit. Koitetaan siis selvittää ne ratsun -graafille. Laskeminen eri arvoille paljastaa melko nopeasti säännönmukaisuuden, jonka voi sitten kuvaa katsomalla perustella. Polynomi on helppo: nurkilla on jokaisella 2 naapuria, näiden viereisillä (:lla) reunaruuduilla 3, muilla ():lla reunaruuduilla 4. Toisella rinkulalla käy näin: nurkat: 4, muut reunat (): 6. Sitä sisemmillä on kaikilla täydet 8 naapuria. Saamme siis, kun
Pienemmille :n arvoille tämä ei toimi, mutta niille koko ongelma voidaan laskea brute-forcena.
Sitten polynomi . Tämä vaatii hieman enemmän pähkäilyä, mutta tapaus oli meillä jo edellisessä postauksessa esimerkkinä. Itse asiassa tuo tapaus on juuri viimeinen, jossa yleinen kaava ei tule vielä käytäntöön. Arvoille pätee
Lisäksi tarvitsimme vielä polynomin . Se saadaan, mikäli , kaavalla
Ainoa tapaus, jota emme ’itten laskemisesta vielä käsitelleet aimmissa postauksissa on 4-syklit eli . Mutta näiden lukumäärälle löytyy kaava mathworldista: Jos , niin , muuten . (Meidän täytyy kertoa mathworldin kaava :lla, sillä tutkimme järjestettyjä nelikoita.)
Huomataan, että, kun , laskenta-algoritmit tuottavat :stä riippuvan, astetta olevan polynomin, joten sen kertoimet voidaan selvittää laskemalla interpolaatio-polynomi tarpeeksi monelle pisteelle. Saadaan
Huomataan, että tämä kaava toimii itse asiassa, kun . Pienemmät arvot eivät noudata tätä kaavaa, mutta niille voidaan keksiä kaava
ja näin on ratkaisu saatu kahdessa palassa määrättyä.
Tämä kyseinen jono löytyy OEIS:sta. Jonon :s termi on ratsun -graafin riippumattomuus polynomin neljännen asteen kerroin.
Tehtäviä
Ratkaise vastaava tehtävä, kun laudalle asetetaan 2 valkeaa ja 2 mustaa ratsua. Nyt sallittuja tilanteita ovat ne, joissa eriväriset eivät uhkaa toisiaan. (Vinkki: valitse tietyt joukot .)
Yleistyksiä: -laudalle, :lle ratsulle, eri väriselle ratsulle. Myös eri nappuloille (huom. rajoituksen että nappi ei voi mennä toisen yli voi unohtaa. Uhkaamattomassa tilanteessa sillä ei ole väliä, koska muiden nappuloiden liikkeet ovat lineaariset ja ne uhkaavat jo edessä olevaa nappulaa, jos joutuisivat kulkemaan sen läpi uhatakseen toista).
Termin kerroin kertoo kuinka monta -asteista solmua graafissa on eli kuinka monella on (tasan) naapuria. Esimerkki:
Polynomista voidaan laskea graafin kaarien määrä laskemalla
Tämähän on tuttu kaava, että solmujen asteiden summa on kaksi kertaa kaarien määrä. Polynomi kuitenkin ryhmittelee solmut asteen mukaan, joten se on kätevä laskuissa. Esimerkin graafissa lasku on
Mielenkiintoisempi ja enemmän graafista tietoa antava polynomi on toinen naapuruus-polynomi. Muuttujia on mielivaltaisen monta, mutta kuitenkin tietyssä polynomissa aina vain äärellisen monta. Se määritellään seuraavasti.
Tutkitaan jokaiselle solmulle sen naapureita ja muodostetaan termi . Nämä summataan yhteen. Esimerkki:
Keltaisella korostetut solmut tuottavat saman termin. Niillä sattuu tässä esimerkissä olemaan täysin samat naapurit, mutta samanlaiset termit voi syntyä muutenkin, riittää että naapuruston ”tyyppi” eli asteiden suurusjärjestykseen järjestetty vektori on sama.
Polynomin avulla voidaan laskea esimerkiksi kolmio-vapaan (ei sisällä 3-sykliä) graafin 4-ketjujen lukumäärä seuraavasti: Olkoon 4-ketju . Kiinnitetään . Nyt ja ovat jotkin sen naapurit. Annetaan siis niiden juosta kaikkien naapuri-kaksikkojen , läpi. Lopuksi täytyy olla jokin :n naapuri. Tässä graafin kolmio-vapaus tulee tarpeeseen: meidän täytyy tietää vain se kuinka monta naapuria :lla on :n lisäksi. Solmu ei voi olla :n naapuri sillä muuten muodostuisi kolmio. Seuraava JavaScript funktio suorittaa tämän laskun, kun sille antaa polynomin objektina, johon termin kerroin on koodattu avaimen ”” arvoksi (huom. jokainen on siis avaimessa niin monta kertaa kuin se termissä esiintyy eli ”potenssit kirjoitetaan tuloina”).
/** @param p: The second neighbor polynomial of the graph */
function count4PathsInTriangleFreeGraph(p) {
let s = 0;
for (let key in p) {
let a = key.split(",").map(x=>+x);
for (let k1=0; k1<a.length; k1++) {
for (let k2=0; k2<a.length; k2++) {
if (k2===k1) continue;
//x4 can be any nbr of x3 except x2 (hence -1)
//x1 can't be a neighbor of x3, since no triangles
s += p[key]*(a[k2]-1);
}
}
}
return s;
}
Edellisen esimerkin graafi on kolmio-vapaa, joten funktiota voi käyttää. Parametriksi annettava objekti on:
Koodi antaa tulokseksi 60. Näin pienessä esimerkissä tämän funktion hyödyllisyys ei ehkä tule ilmi; voisimmehan käydä kaikki solmu-nelikot läpi ja tarkastaa kuinka moni niistä on 4-ketju. Sen hyödyt nähdään suuremmille graafeille, joille polynomi on laskettavissa helposti ja sen termien lukumäärä säilyy pienenä (eli niissä on jokin sellainen säännönmukaisuus joka helpottaa laskemista). Esimerkki:
Ratsun graafi 8×8-laudalla. Säännönmukaisuuksia merkitty väreillä. Tässä esimerkissä sitä ei vielä itse asiassa näe, mutta kun laudan koko kasvaa yli 8, keskelle syntyy neliö, jonka kaikki solmut ovat :n mielessä samanlaisia: niillä on 8 naapuria, joilla jokaisella on 8 naapuria. Suurilla n termi tulee siis hallitsevaksi: sen kerroin on .
Huomioita
Jos polynomissa asettaa kaikilla , niin saadaan polynomi . Tämä johtuu siitä, että jaottelee solmut tarkemmin niiden naapureiden asteiden mukaan, kun vain solmun oman asteen mukaan. Jokainen solmu tuo :een astetta oleven -termin, joten asetettassa , tämä redusoituu :ksi, kuten :ssä.
Kun edellä puhuttiin 4-ketjuista, tarkoitettiin vain solmu-nelikkoja, joiden välillä on kaaret ensimmäisestä toiseen, toisesta kolmanteen ja kolmannesta neljänteen. Muista kaarista ei välitetä (esim. vaikka neljännestä olisi takaisin ensimmäiseen). Ei siis puhuta indusoiduista ketjuista.
Kolmio-vapaus on oleellinen vaatimus 4-ketjulaskelmassa. Esimerkiksi 3-solmuiselle täydelle graafille , joka on itse kolmio, saadaan , joka antaisi tulokseksi 4. Mutta tietenkään siellä ei voi olla yhtään 4-ketjua, kun solmuja on vain 3.
Funktiota count4PathsInTriangleFreeGraph voi yksinkertaistaa jättämällä k1:n iteraation pois ja vain kertomalla sisimmässä silmukassa lisättävän lukumäärän (a.length-1):llä.
Tehtäviä
Laske graafista sellaisten solmu-kolmikkojen lukumäärä, joille ja ovat rinnakkaiset (eli niitä yhdistää kaari) sekä ja ovat rinnakkaiset.
Entäpä vastaavasti nelikot, jossa kaikki ovat rinnakkaisia :lle?
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 .