26 Ocak 2014 Pazar

Problem 76 - Counting Summations



Çözerken zevk aldığım problemlerden biri de problem 76 oldu. Gerek dinamik programlamayla soruyu ufak parçalara ayırıp çözülebildiği gibi, formülleyerek de çözüme gitmek mümkün. Formülü bulmak bana daha eğlenceli geldi açıkçası, her ne kadar dinamik programlamaya göre zahmetli de olsa bu bir bulmaca.

Soru

5'i şu şekilde, 6 farklı ifadeyle yazmak mümkün
4+1
3+2
3+1+1
2+2+1
2+1+1+1
1+1+1+1+1

Peki 100 sayısını bu şekilde yazmak istersek kaç farklı ifade ortaya çıkar? (En az iki pozitif sayının toplamı şeklinde olmalı)


Çözüm

Formülleme

Sayımız n ise, p(n) fonksiyonu bize n sayısının kaç farklı pozitif toplam şeklinde yazılabileceğini versin.

Bu durumda araştırma yaptığınızda görüyorsunuz ki, p(n) değerleri esasında "Beşgensel Sayı Teoremi" - "Pentagonal Number Theorem" ile örtüşüyor, fakat değerler bir eksik. Sebebi ise sanıyorum bu teoremde, işleme n+0'ın da katılıyormuş gibi farz edilmesi. Dolayısıyla hesaptan 1 çıkaracağız.

Pentagonal Number Theorem hakkında buradan daha fazla bilgi edinebilirsiniz.
http://en.wikipedia.org/wiki/Pentagonal_number_theorem

Genellediğimizde şu sonuçlarla karşılaşıyoruz

p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + ...

Formülsel ifade edecek olursak




k = 1, -1, 2, -2, 3, -3...
gk = 1, 2, 5, 7, 12


p(0) = 1
p(1) = 1
p(2) = 2
p(3) = 3
p(4) = 5
p(5) = 7
p(6) = 11
p(7) = 15
p(8) = 22
p(9) = 30
p(10) = 42
p(11) = 56
p(12) = 77

p(5) = p(4) + p(3) - p(0)
7 = 5 + 3 - 1

p(6) = p(5) + p(4) - p(1)
11 = 7 + 5 - 1

p(7) = p(6) + p(5) - p(2) - p(0)
15 = 11 + 7 - 2 - 1

p(8) = p(7) + p(6) - p(3) - p(1)
22 = 15 + 11 - 3 - 1

p(9) = p(8) + p(7) - p(4) - p(2)
30 = 22 + 15 - 5 - 2

p(10) = p(9) + p(8) - p(5) - p(3)
42 = 30 + 22 - 7 - 3

p(11) = p(10) + p(9) - p(6) - p(4)
56 = 42 + 30 - 11 - 5

p(12) = p(11) + p(10) - p(7) - p(5) + p(0)
77 = 56 + 42 - 15 - 7 + 1


Kodlama

int hedef = 100; int sayici = 0; int _k = 1;
int[] k = new int[hedef+1];
int[] gk = new int[hedef+1];
int[] p = new int[hedef+1];

// hedef'e kadar olan gk'lar için k'ları belirliyoruz
// gk = k(3k-1)/2 for k = 1, -1, 2, -2, 3...

while (sayici < hedef)
{
   if (sayici % 2 == 0) // 1, 2, 3, ...
   {
      k[sayici] = _k;
   }
   else // -1, -2, -3, ...
   {
      k[sayici] = (-1) * _k;
      _k++; // Negatif değer verildikten sonra k +1 artıyor
   }
   // Sonuç k[] = {1, 1, 2, -2, 3, ...}

   sayici++;
}

// k'lardan yola çıkıp gk'ları belirliyoruz

for (int q = 0; q < hedef; q++)
{
   // gk = k(3k-1)/2 ---> Beşgensel sayılar
   gk[q] = (k[q]) * (3 * k[q] - 1) / 2;
}

// Şimdi p(n)'leri belirleyebiliriz, eldeki gk değerlerini kullanacağız
// p(n) = S [(-1)^(k-1) . p(n-gk)] --> S for Sigma

int c = 0;
p[0] = 1; p[1] = 1; p[2] = 2; p[3] = 3; p[4] = 5;

for (int n = 5; n <= hedef; n++)
{
   c = 0;
   while (n - gk[c] >= 0)
   {
      p[n] += (Int32)(Math.Pow((-1), (k[c] - 1)) * p[(n - gk[c])]);
      c++;
   }
}

p[n] dizisi, 100'e kadar olan sonuçları 1'er fazla olacak şekilde barındırmaktadır.(Soruda istenilene göre 1 fazla, teoreme göre doğru)

Dinamik Programlama

Esas problemi, ufak alt problemlere ayırarak çözümü daha kolay hale getirmek mümkün. Bunun için dinamik programlamaya göz atmakta fayda var. Çözüm çok daha kısa görünecektir.
int[] ifadeler = new int[hedef + 1];
int hedef = 100;
ifadeler[0] = 1;

for (int i = 1; i <= 99; i++)
{
   for (int j = i; j <= hedef; j++)
   {
      ifadeler[j] += ifadeler[j - i];
   }
}

Sonuç : 190569291 bulunur.




Hiç yorum yok:

Yorum Gönder