Wednesday, December 26, 2012

DUNNO1 - бодлогуудын анализ

А:

Энэ тодорхой ойлгомжтой дасгал байсан, алдах хүн байхгүй байлгүй.



int main() {
string a;
cin >> a;
for (int i = 0; i < (int)a.size(); ++i) {
if (string("CAMBRIDGE").find(a[i]) == string::npos) {
cout << a[i];
}
}
cout << endl;
}


B:

Эхлээд мэдэгдэж байгаа дүнгүүдээ буурахаар эрэмбэлье. Одоо дээрээс нь эхлэн оролцогч бүр дээр түрүүлэх боломжтой эсэхийг нь шалгая, К дугаар оролцогч гэж үзье. Энэ оролцогч түрүүлэхийн тулд бусад оролцогчидод хэдэн оноо өгөх вэ л гэсэн бодлого юм. Дараах Greedy санааг ашиглана. К дугаар оролцогчоо түрүүлүүлэхийн тулд шууд N оноог өгөх нь ойлгомжтой. Буурахаар эрэмбэлсэн учир К-н доор байгаа хүмүүс бидэнд огт хамаагүй, тэд нарт хэдэн ч оноо нэмээд К дугаар оролцогчийн дээр гарч чадахгүй. Харин К-н дээр байранд байгаа хүмүүсийг К-н дээр байлгахгүй тулд аль болох хамгийн бага оноог өгөх ёстой. Дахиад сайн бодъё, К-н дээр байгаа хамгийн өндөр оноотой тамирчинд 1 оноог өгнө, хамгийн өндөр оноотой 2 дахь тамирчинд 2 оноо гэх мэтчилэн К-1 дахь тамирчинд К-1 оноо өгнө. Тэгвэл уг К дугаар тамирчин түрүүлэх зайлшгүй бөгөөд хүрэлцээтэй нөхцөл нь:

 a[K] + N ≥ max{ a[1] + 1, a[2] + 2, …, a[K - 1] + K - 1 }, энд а[] массивт оролцогчидын оноог буурахаар эрэмбэлсэн байгаа.

Одоо гол асуудал нь К-н өмнөх оролцогчидын хамгийн их оноотойг яаж олох вэ? Үүнийг жижигхээн DP ашиглаад шийдчиж болно, үүнийг уншигч таньд нээлттэй үлдээе.


Бодолт: O(NlogN)




#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

int a[300000];

bool cmp(int x, int y) { return x > y; }

int main () {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
scanf("%d", a+i);
sort(a, a+n, cmp);
int s = 0;
int maX = 0;
for (int i = 0; i < n; ++i) {
s += (a[i] + n >= maX);
maX = max(maX, a[i] + i + 1);
}
printf("%d\n", s);
system("pause");
return 0;
}




C:

Өгөгдсөн N тооны length(N) = (N, K, ... ) гэсэн тоонуудаас бүтсэн байг. Хэрвээ сайн бодож үзсэн бол яг К буюу өөрийг нь хуваадаггүй хамгийн бага тоо, хэдий N<=10^17 ч маш бага тоо байгаа. Миний бодлогоны хувьд 50-аас хэтрэхгүй, гэхдээ Хонгор 41 гэж аваад давуулсан байна лээ, хангалттайлдаа уг нь кк (энийг яаж шалгах вэ?? бодож үзээрэй). Одоо тэгвэл бодлого маань ерөөсөө ийм, тэр К гэдэг тоо [A ; B] завсар дахь хэдэн тооны length()-т яг тэр байгаа байрлалдаа орох вэ? Тийм тоонууд маань бүгд Length(N) = Length(K)+1 нь ойлгомжтой.
К-г багтаасан тоонуудыг яаж тоолох вэ?
[A ; B] завсарт орших өөрийг нь хуваадаггүй хамгийн бага тоог К гэе (энд яг дээрхтэй адилхан К тоог ярьж байгаа шүү), тэгвэл К-аас бага бүх тоонууд тухайг тоог хуваана гэсэн үг. Энэ нь LCM - Хамгийн бага ерөнхий хуваагдагч, LCM(1, 2, 3, ..., K-1) мөн тухайн тоог хуваана гэсэн үг. Нэмэлт шаардлага бол тухайн тоо К-д хуваагдахгүй байг ёстой. Энэ хоёр нөхцөлийг хангах тоонуудыг тоолоод L гэе. Тэгвэл бодлогын хариу Нийлбэр{ (length(K)+1)*L } энд K = 2..41 байхад хангалттай.
Одоо тэдгээр тоонуудыг яаж тоолох талаар хальт санаа өгье. [A;B] завсар дахь тоонуудаас LCM(1, 2, 3, .., K-1)-т хуваагддаг тоонуудыг тоолсон гэж үзье. Эдгээр тоонуудаас К-д хуваагддагийг хасна, мөн огтлол буюу К болон LCM(1, 2, 3, ..., K-1)-т хуваагддагийг хасах ёстой, энэ чинь юу гэсэн үг вэ гэхээр LCM(1, 2, 3, ..., K)-г олоод хасчина гэсэн үг. Ерөнхийдөө бол А болон В-д харьяалагдах D-д хуваагддаг тоог тоолох ба дараах томёо хүчинтэй: B / D - (A - 1) / D, Үүнийг яагаад гэдэгийг өөрсдөө оролдож хараарай, уг нь энгийн л зүйллдээ.





#include <iostream>
#include <vector>
using namespace std;

int main(){
long long A, B, ret = 0;
cin >> A >> B;

// LCM = lcm(1, 2, ..., K-1)
long long LCM = 1;

int length[42];
length[2] = 1;

for (int K = 2; K < 42; K++){

for (int i = 2; i < K; i++) {
if (K % i != 0) {
length[K] = length[i] + 1;
break;
}
}

long long tempLCM = LCM;
vector<int> lcmNumbers;
for (int p = 2, k = K; k > 1; p++) {
if (k % p == 0) {
lcmNumbers.push_back(p);
while (k % p == 0) {
k /= p;
}
}
}
if ((int)lcmNumbers.size() == 1) {
tempLCM *= lcmNumbers[0];
}

ret += (length[K] + 1) * (B/LCM - (A-1)/LCM);

if (tempLCM > B || tempLCM < 0) break;
ret -= (length[K] + 1) * (B/tempLCM - (A-1)/tempLCM);

LCM = tempLCM;
}
cout << ret << endl;
}




D болон E::

Энэ хоёрын бодолт бол хоюулаа тодорхой шууд л Bruteforce хийгээд тоол, харийн өөрсдийн чинь код бичих чадвараас л шалтгаалж хүнд хөнгөн нь тодорхойлогдоно.



D бодолт:



    int n, m;
cin >> n >> m;
int a[n];
for(int i=0; i<n; i++) cin >> a[i];
int sol = 0;
for(int i=0; i<n; i++)
for(int j=i+1; j<n; j++)
for(int k=j+1; k<n; k++)
if( a[i]+a[j]+a[k] <= m )
sol = max( sol, a[i]+a[j]+a[k] );
cout << sol << endl;
return 0;




E бодолт:






char a[505][505];
int sol[5];

int main()
{
int m, n; scanf("%d%d", &m, &n);
for(int i=0; i<5*m+1; i++) scanf("%s", a[i]);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
{
int x = 1 + 5*i;
int y = 1 + 5*j;
int k = 0;
for(int l=0; l<4; l++)
k += (a[x+l][y] == '*');
sol[k]++;
}
for(int i=0; i<5; i++)
printf("%d%c", sol[i], i==4?'\n':' ');
}




F:



Энэ бодлогыг бодлогонд Stack эсвэл рекурс өгөгдөлийн бүтэц мөн BitMask 2-г ашиглана. Эхлээд өгөгдсөн тэмдэгт мөртөө нэвтрэлт хийгээд бүх зөв хаалтуудыг Стек-даа хийгээд ав, байрлалаар нь, тэгээд битмаскаа ашиглаад бүх боломжоор нь хариугаа гаргаж аваад эрэмблэхэд бодогдоно. Кодоо цэвэрхэн бичихгүй бол TLE авахуулах тестүүд их байгаа.



#include <iostream>
#include <string>
#include <set>
#include <vector>
#include <stack>

#define FORC(it, C) for(__typeof((C).begin()) it = (C).begin(); it != (C).end(); ++it)

int main(void) {
std::string ss;
std::cin >> ss;
int n = ss.size();
std::vector< int > pos(n, -1);
std::stack< int > current;
int numOfBar = 0;

for (int i = 0; i < n; ++i) {
if (ss[i] == '(') {
pos[i] = numOfBar++;
current.push(pos[i]);
}
if (ss[i] == ')') {
pos[i] = current.top();
current.pop();
}
}

std::set< std::string > ret;

for (int i = 1; i < (1<<numOfBar); ++i) {
std::string temp ;
for (int j = 0; j < n; ++j) {
if ((ss[j] != '(' && ss[j] != ')') || !(i & (1 << pos[j]))) {
temp += ss[j];
}
}
ret.insert(temp );
}

for (auto &retOut: ret) {
std::cout << retOut << std::endl;
}

return 0;
}




Дараагийн хоёр бодлого буюу G, H хоёр дээр дахин шинэ пост зориулна. Бас С бодлого дээр би өөр маш сонирхолтой бодолт хийсэн байгаа. Тэрийгээ давхар сонирхуулнаа.

  Код үлдээх уг нь муу, гэхдээ яахав сурах нэг нь сурах юм байвал сураад, сурахгүй нэг нь шууд хуулаад илгээнэ биз, надад хамаагүй асуудал.

Tuesday, December 25, 2012

Блогийн нээлт бас тэмцээн

Сайн байцгаана уу

Хүмүүс блог, хувийн сайт нээнгүүтээ нээлтээ хийдэг байхаа. Миний хувьд тэгж бодоогүй, сураггүй нээх шууд нээнгүүтээ тиймэрхүү зүйл бичих тохиромжгүй санагдаж байсан юм. Хэрвээ санаж байгаа бол энэ блогийн хамгийн эхний пост some day гэсэн үг байгаа. Цаг нь болсон юмшиг байна хэхэ.

Анх 2011 оны 12-р сарын 7-нд блогоо нээж байсан юм. Блог нээх хэд хэдэн шалтгаан байсан. Би өөрийгөө зүгээр л энгийн дундаж чадвартай хүн гэж үздэг учраас, юм сурах зовлонг мэднэ. Бүр сурах гээд хүсээд байхад эх хэл дээр минь материал, заах хүн байхгүй байхын зовлонг бүр ч мэднэ. За тэгээд байсан ч ойлгомжгүй болих тайлбарласан бас буруу орчуулсан материал, ном үзэж их цаг бардаг байлаа. Дээрээс нь тухайн алгоритм онолыг ойлгосны дараа надад хүн ингээд хэлээд өгцөн бол ойлгочих байж гэж бодогддог байлаа. Энэ зүйл ядаж бага биш ч хүмүүст тохиолддог зүйл гэж бодож байна. Би өөрөө бол тийм ч сайн бодлого боддоггүй, гэхдээ энэ сурсан бага ч гэсэн зүйлээ шинэ соргого дээр нь хойч үедээ мөн сурах гэж хичээж, зовж байгаа бүх хүмүүст эх хэл дээр нь төвөггүй уншаад ойлгох зүйл үлдээе гэж байнга хүсдэг маань энэ блогийн хамгийн том оршил юмшүү. Тиймээс бичсэн зүйлээ ойлгомжтой энгийн ямар ч тэнэг хүн уншаад ойлгомжтой байх тал дээр үнэхээр их цаг гаргадаг. Гэхдээ үр дүнтэй байгаа эсэхээ одоо хүртэл мэдэхгүй байна, уг нь блогийн хандалт маш их байгаа, даанч сэтгэгдэл үлдээж, санал гомдол бичдэг хүн байхгүй болохоор тэр. За олон юм нуршихаа болъё, ямар ч байсан блогийхаа нээлтийг хийж байгаадаа баяртай байна хэхэ.
www.spoj.pl/DUNNO Энэ сайт дээр блогтоо бичсэн алгоритмтай холбоотой бодлого, хичээлүүдийн бодлого тавигдах болно.
-----
Тэмцээн:
Энэ удаагийн тэмцээнд нийт 15-н оролцогч оролцлоо. Дажгүй асуудалгүй сайхан л болох шиг болсон. Дүн дараах байдалтай байна.



Эхний 3-н байр Хонгор, Сугардорж багш мөн Золжаргал 3т баяр хүргэе.
Энэ удаагийн тэмцээний хувьд ерөнхийдөө 10-н жилийхнийг хэр байгааг харая гэж бодож байсан, нээх 10-н жилийхэн орсонгүй гэхдээ орсон хэдийн хувьд бэлтгэлээ сайн хийгээрэй гэж хэлмээр санагдсан. Бодолтуудыг дараагийн постоор оруулна.
Энэ постоо өндөрлөхөөсөө өмнө энэ чиглэлээр сонирхож явах хүүхдүүд мөн хэрэгтэй хүмүүүст нь өөрийн зүгээс зөвлөгөө хэлмээр байна.

 Олимпиад бодож битгий бодлого бод, зүгээр л бодлогондоо анхаар. Ямар нэгэн алгоритм бодлого бодож байгаа бол өөрийгөө тухайн зүйлдээ бие даагаад судалгаа шинжилгээний ажил хийж байна гэж үзээд хэдэн ч цагаар хамаагүй суу. Нэмээд хэлэхэд юм хийхэд хэр их цаг зарцуулж суусандаа биш хэр үр дүнтэй сууснаар чинь чиний мэдэх зүйл тодорхойлогдоно. Бодлого бодох нэрээр ком дээр сууж ФБ нээж тэтрис тоглох, охидуудтай чатлахыг хэлэхгүй шүү хэхэ, дэмий сууж нуруугаа ядраасан нэг цагаас бодлого, алгоритмандаа анхаарч төвлөрч чадсан 10 минут ч илүү байх. Мөн ямар нэгэн бодлого бодсоныхаа дараа энэ бодлогоос юуг сурч авав гэдэгээ байнга эргэж хар. Ганц хоёр бодлого бодчоод битгий бардаж хөөрөөрэй, миний ажигласнаар хүүхдүүд хэдэн бодлогоны ард гарчаад их зан гаргаад багш нараа ч тоохгүй өөр зүйл хийдэг, энэ бол хамгийн буруу зүйл. Өөр хоёр нэмэх зүйл бол Математик мөн Англи хэл. Энэ хоёрыг ер нь алгоритм эхлэхээсээ өмнө 1 жил зарцуулаад ороход буруудах юм байхгүй дээ. Гэхдээ хүн хүн өөр, сурах арга барил ондоо, юм ойлгох чадвар ялгаатай байдагаас хамаараад яг 1 жил ч шаардахгүй байх. Зүгээр онолын ч гэх юмуу, сонирхсон зүйлээ миний иймэрхүү блог эсвэл хүмүүсээс асуугаад мэдчиж болно, гэхдээ бүр гүнзгийрэх гэвэл яах аргагүй математик мэдлэг сэтгэлгээ мөн хангалттай материал байхгүйгээс гадны ном сайт унших хэрэг гарна тэгэхээр англи хэл зайлшгүй болж байгаа юм. Хэл муу, мат сэтгэлгээ мэдлэг тааруугаасаа болж маш их цаг алдаж байна. За өөр ч болох шивдээ, нэг зүйлийг нэмж хэлэхэд хүмүүсээ хүнд баярлалаа гэж хэлж сураарай, би энэ блогийг нээгээд мөн цөөнгүй тэмцээнийг зохиогоод өдий хүртэл баярлалаа гэдэг үгийг маш цөөхөн сонссон, тэмцээн зохиох бэлтгэл ажил бодлого зохиох тестлэх гээд ямар их цаг авдаг гээч.

За түр баяртай.

Saturday, December 15, 2012

Warm up

Тэмцээн эхэлтэл, мѳн тэмцээнийхаа шалгагчтай танилцангаа дараах бодлогыг бодож байна уу.

http://www.spoj.com/DUNNO/problems/TUGS/

Ер нь бол http://www.spoj.com/DUNNO/ энэ саит дээр би бодлогууд тавьж байх болноо. Бодлоготой холбоотой санал хүсэлт, асуух зүйлээ коммэнт үлдээж асууж болно шүү.

UPDATE

Спож асуудалтай байгаа тул тэмцээнээ 3-н хоногоор хойшлууллаа. Уучлаарай хүмүүсээ. Монголын цагаар 25-ны орой 8-д болно, 5-н цагийн хугацаатай. Өөр улс оронд байгаа хүмүүс эндээс цагаа харна уу. Болох газар хуучнаараа байх байхаа. За асуудал гаргасанд уучлаарай.

Friday, November 30, 2012

Тэмцээн - UPDATE

За сайн байцгаана уу?

Одоо нэг юм криллээр бичих боломж гарлаа. Тэмцээний хувьд Програмчлалын лиг 4тэй зэрэг болно. Гэхдээ мэдээж дүнгээ 10-н жил болон бүгд гэж 2 тусад нь гаргана. Тэмцээн 12-р сарын 22-ны хагас сайн өдрийн өглөө 10-н цагаас эхлэнэ, 5 цагийн хугацаатай 10-аас дээш бодлоготой байх болноо. Програмчлын Лиг-т орж байгаа хүүхдүүд хуучнаараа МУИС дээрээ очдогоороо очих ёстой шүү!!. Тэмцээн энд болох болно, тест тоолдогоор тохируулсан байгаа. Өөр улс орон байгаа хүмүүс эндээс болох цагийг харж алба ажилаа тохируулна уу. Бүх бодлогуудыг би өөрөө бэлднэ. За өөр мартсан юм байхгүй байхаа, асуух зүйл байвал доор үлдээгээрэй, нээрээ орох хүмүүс бүртгүүлэх шаардлагагүй шүү.



UPDATE

Спож асуудалтай байгаа тул тэмцээнээ 3-н хоногоор хойшлууллаа. Уучлаарай хүмүүсээ. Монголын цагаар 25-ны орой 8-д болно, 5-н цагийн хугацаатай. Өөр улс оронд байгаа хүмүүс эндээс цагаа харна уу. Болох газар хуучнаараа байх байхаа. За асуудал гаргасанд уучлаарай.

Friday, July 27, 2012

Bodooroi







Answer geed helee songood, submit hiigeerei. Hall of fame-eer ni bodson humuus garna shuu! Oo
rsdiin zohioson algorithm-uudaa comment-eer bichvel bayrlana shuu!

Friday, June 8, 2012

Мод

Энэ удаагийхаа постоор модны бодлогын талаар зөвлөмж өгөөд, IOI 2010 - Traffic Congestion, Модтой тоглоё 1 мөн дараагийн постуудад IOI 2011 Race, IOI 2009 Region зэрэг бодлогуудын бодолтыг бас бусад бодолтуудын хамт заахыг хичээе.



Модны бодлого ихэвчлэн Динамик Програмчлал, Рекурс, Гүний нэвтрэлт, Divide and Conquer-аар бодогддог. Тэгээд хамгийн чухал зүйл бол Рекурсыг маш сайн ойлгосон байх хэрэгтэй. Рекурсын буцал, дуудалтыг ашиглан маш гоё бодолт, мөн том том алгоритмууд бичигддэг.

Модыг эхлээд аль нэг оройгоос нь өлгөөд доош нь цувуулсан байдалтай төсөөлөх хэрэгтэй, үүнийг бид Rooted Tree гэдэг. Чанар ёсоор аль ч оройгоос өлгөсөн ялгаагүй. Жишээ нь доорх модыг 3 гэсэн оройгоос нь өлгөсөн байна.





 За шууд дараах 2 бодлого дээр бодолт хийе:

Модтой тоглоё 1:

Бидэнд чиглэлгүй, жинтэй мод өгөгдсөн бол модны жинг ол. Энд замын жин гэж тухайн зам дээр байгаа бүх ирмэгүүдын жингүүдийн үржвэр. Харин модны жин гэж бүх хос оройг холбох замуудын жингийн нийлбэрийг хэлнэ. Хариуг 10^9+7 жиш. Бүтэн өгүүлбэрийг эндээс уншиж болно.





Дээрх жишээнд бид Х гэсэн оройн хоёр дэд модын жингүүдийг олчихсон тус тус w1, w2 гэж үзье. Тэгвэл одоо Х орой орсон бүх замуудын жинг олохын тулд дараах 3-н тохиолдлыг авч үзнэ. Х орой зүүн дэд модонд орсон бүх замуудын, мөн баруун дэд модонд орсон, мөн 2 дэд модонд хоюуланд нь орсон гэж тус тусад нь олоод нэмэхэд бодлогын хариу гарна.

Х орой зүүн дэд модонд оросн замуудтай хариуг сонирхоё. w1*c1, баруун дэд модны хувьд w2*c2, хоюуланд нь орсон гэвэл w1*w2*c1*c2. Бүтэн бодлогын хариу w1*c1+w2*c2+w1*w2*c1*c2. Код-оо бичихдээ дараахь зохицуулалтыг хийе. dp[i]-ээр i орой орсон замуудын жин ба мэдээж i оройн доор орших модонд харгалзах. Дээрх жишээн хувьд w1*c1+w2*c2 нийлбэрийг хадгалж байна гэсэн үг. dfs(i) функц маань i үндэстэй дэд мод болгоны хувьд жинхэнэ хариу буюу дээрх жишээн хувьд w1*c1+w2*c2+w1*w2*c1*c2 нийлбэрийг хадгална. Тэгэхээр бодлогын хариу бүх дэд модны хувьд нийлбэр гарна. Мэдээж энэ бүх үйлдэлүүд дурын оройгоос рекурс дуудалт хийсний дараа буюу рекурс буцалт хийх үед хийгдэнэ. Дээрх тохиолдолд би дээр зөвхөн жишээ авсан ба орой бүрийн хувьд 2 зангилаатай байна гэж байхгүй. Тэгэхээр рекурс дуудалт бүрт бодолт хийнэ гэсэн үг. C++ код:




#define mp make_pair
#define pb push_back
using namespace std;
long long toint(string s) { istringstream in(s); long long x; in>>x; return x; }
template<class T> string tostring(T x) { ostringstream out; out<<x; return out.str();}
vector< pair<int, int> >v[100010];
long long dp[100010];
long long dfs(int dad, int child){
long long ret = 0;
dp[child]=1ll;
for(vector< pair<int, int> >::iterator it=v[child].begin(); it!=v[child].end(); it++){
int u = it->first;
if(dad==u)continue;
ret = (ret + dfs(child, u))%1000000007;
long long subtree = (dp[u]*it->second)%1000000007;
ret = (ret + (dp[child]*subtree)%1000000007)%1000000007;
dp[child] = (dp[child] + subtree)%1000000007;
}
return ret;
}
int main(int argc, char *argv[]) {
//freopen("mtree.in", "r", stdin);
//freopen("mtree.out", "w", stdout);
int n;
scanf("%d", &n);
for(int i=0; i<n-1; i++){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a--, --b;
v[a].pb( mp(b, c) );
v[b].pb( mp(a, c) );
}
printf("%I64d\n", dfs(-1, 0));
return 0;
}


IOI 2010 - Traffic Congestion: Монгол өгүүлбэрийг эндээс

Модоо мөн л Rooted Tree болгоё. dp[i]-ээр i дугаар орой дээр цэнгэлдэхийг барьсан гэвэл үүсэж болох хагмийн их замын түгжрээг тэмдэглэе. Бүх оройн хувьд олж чадвал эдгээрийн хамгийн бага нь хариу болно. m-ээр i орой эцэг нь болох бүх моднуудын гарч болох замын түгжрэлийн нийлбэрийг хадгалая. Үүнийг юун ашиглах болохыг доорх зургаар харуулая:





 C++ code:




#define mp make_pair
#define pb push_back
using namespace std;
long long toint(string s) { istringstream in(s); long long x; in>>x; return x; }
template<class T> string tostring(T x) { ostringstream out; out<<x; return out.str();}
vector<int>fans;
vector<int>v[1000010];
int dp[1000010];
int sum;
int dfs(int dad, int child){
int m=0;//
int ret = fans[child];
for(int i=0; i<v[child].size(); i++){
int u = v[child][i];
if(u==dad)continue;
int temp = dfs(child, u);
m = max(m, temp);
ret += temp;
}
m = max(m, sum-ret);
dp[child] = m;
return ret;
}
int main(int argc, char *argv[]) {
int n;
freopen("traffic.in", "r", stdin);
freopen("traffic.out", "w", stdout);
scanf("%d", &n);
for(int i=0; i<=n; i++){
int t;
scanf("%d", &t);
sum += t;
fans.pb(t);
}
for(int i=0; i<=n; i++){
int x, y;
scanf("%d%d", &x, &y);
v[x].pb(y);
v[y].pb(x);
}
dfs(-1, 0);
for(int i=0; i<=n; i++)printf("%d\n", dp[i]);
//system("pause");
return 0;
}




Тайлбарлахад их төвөгтэй байнаа. Ойлгомжгүй байвал уучлаарай. IOI бэлдэж байгаа хүүхдүүдэд зөвлөхөд Codeforces, topcoder-н графуудыг эхлээд дуусгаачээ :)

Thursday, May 31, 2012

Хамгийн Бага Үнэлгээт Мод (Minimum Spanning Tree) + Union Find

Бидэнд чиглэлгүй жинтэй граф өгөгдөв. Тэгвэл зарим нэг ирмэгийг хасаж шинэ мод үүсгэх ба үүссэн шинэ мод нь бүх оройг агуулсан мөн жингүүдийн нийлбэр хамгийн бага байх ёстой. Доор зургаар тодорхой харуулав.



Бодлого ойлгомжтой байх. Энэ бодлого нь Хамгийн Бага Үнэлгээт Мод буюу Minimum Spanning Tree гэх графын алгоритмуудын сонгодог бодлогуудын нэг. Мөн алгоритмыг ойлгоход болон бичихэд амархан.

Ерөнхийдөө одоогийн байдлаар Greedy санаан дээр тулгуурласан Крускалын алгоритм мөн Примын алгоритм гэх хоёр ерөнхий алгоритм байгаа. Хоорондоо бараг адилхан, гэхдээ ашиглаж байгаа өгөгдөлийн бүтцээрээ ялгаатай.

Энэ удаад Крускалын алгоритмыг тохирох өгөгдөлийн бүтэцтэй нь сонирхуулая.

Эхлээд модны тодорхойлолтоо эргэн саная. Мод гэдэг нь цикл агуулаагүй холбоост графыг хэлнэ. Хэрэв мод нь N ширхэг оройтой бол N-1 ирмэгтэй байна.

Алгоритм дараах энгийн санаан дээр тулгуурлана.

Хэрэв бидэнд N оройтой M ирмэгтэй граф өгөгдвөл N-1 ширхэг хамгийн бага оройг сонгоно. Сонгохдоо тавих шаардлага бол мэдээж сонгож байгаа оройнууд цикл үүсгэх ёсгүй. Яагаад гэвэл бид мод олж байгаа учир. Гол хүндрэл бол орж ирж байгаа оройнууд цикл үүсгэж байгаа эсэхийг хангалттай хурдан шалгах. Одоо хэрэглээ маш өндөртэй өгөгдөлийн бүтэц авч үзье

Union Find::
Union Find-г дараах бодлогон дээр тайлбарлая.
Ангид N оюутан сурдаг ба нийлдэг нийлдэгээрээ груп болон хуваагддаг. Энд транзитив чанар ажиллах ба A B хоёр найзууд, B C хоёр найзууд бол мөн A C хоёр найзууд гэсэн үг. Тэгвэл нийт оюутны тоо N <=10^6 болон хэн хэн найзууд гэдэгийг илтгэх M хос A B өгөгдсөн бол ангид хэдэн груп байгааг ол. Жишээ нь N=5 M=3, 1-2, 5-4, мөн 5-1. Тэгвэл энд дараах хоёр груп үүсэх нь:


Union Find-г үл огтлолцох ойнуудын олонлог гэж төсөөлж болно. Ой бүр нь тухайн ойг заах ганц заагчтай (санах ойн заагч биш зүгээр л тоо, гэхдээ тухайн ойд агуулагдаж байгаа хамгийн бага дугаартай модыг заагчаар сонгох болно!!!). Хоёр ойн заагч нь өөр бол энэ хоёр ой үл огтлолцох ой гэсэн үг.


Union Find-д хийгддэг үйлдэл::

х модны агуулагдаж байгаа ойн заагчийг хайж олох Find(x),

х, у гэсэн хоёр ойг нэгтгэх Merge(x, y)гэсэн хоёр л үйлдэл хийгддэг. Нэгтгэнэ гэдэг маань тус моднуудын агуулагдаж байгаа ойн заагчийг хайж олоод тухайн хоёр ойгоо ганц заагчтай болгоно гэсэн үг.

Дээрх бодлогын өгөгдсөн жишээ нь дээр нь Union Find хэрхэн ажиллахыг харья:

Эхлээд N=5 ширхэг ганц ганц мондоос бүрдсэн 5-н ой байгаа гэж үзье. Бүх ойнуудыг тухайн ганц модны дугаар л заана(i-р ойн заагч нь i тоо өөрөө байна).


Эхний хос орж ирлээ 1-2. Энэ юу гэсэн үг вэ гэхээр 1 болон 2-р модыг агуулсан хоёр ойг нэгтэж нэг заагчтай болго гэсэн үг. Merge(1, 2):




Хоёр дахь хос орж ирлээ 4-5. Merge(4, 5);



Одоогийн байдлаар гурван груп байна.



Гурав дахь хос орж ирлээ. 5-1:



Бодлогын хариу ойн тоо буюу 2.

Энэ өгөгдөлийн бүтэцийг код болгон хэрэгжүүлэхдээ дахин нэг сайжруулалт хийе. rank[i]-р i модны агуулагдаж байгаа ойн өндөрийг тэмдэглэе. Бид Union Find-аа улам хүчтэй, хурдан ажиллуулахын тулд үүсэж байгаа ойн өндөрийг аль болох бага байлгах ёстой. Жишээ нь дараах 3н ойг авч үзье:



Бид I болон J гэсэн заагчтай ойг нэгтгэхдээ аль болох ойн өндөрийг бага байлгах ёстой учраас шууд I модыг J-рүү заалгах хэрэгтэй. Жишээ нь бид I-г шууд M гэсэн модны доор аваачаад өлгөчих юм бол J гэсэн ой хамаагүй өндөр мөн хайлт хийхэд их цаг зарцуулах нь ойлгомжтой харагдаж байгаа байх. C++ дээрх код::
int parent[10001];
int rank[10001];
void createSet(int n){
for(int i=0; i<n; i++)
parent[i]=i;
}
void find(int x){
if(x!=parent[x])
parent[x] = find(parent[x]);//rekursiig butsah zamd ni tuhain mod burt ni oin zaagchiig ni onoono. Ene ni daraa dahin find hiihed barag togtmol hugatsaand hiih bolomj olgono
return parent[x];
}
void merge(int x, int y){
int px = find(x);//X modnii aguulagdaj baigaa oin zaagchiig olj bn
int py = find(y);//Y-n huvid mon adil
if(rank[px]>rank[py])parent[py]=px;//Oin ondoriig hamgiin baga bailgahiin tuld ondor oin zaagch modnoos namhan oigoo olgochihod bolno!
else parent[px]=py;
if(rank[px]==rank[py])rank[py]++;// end unendee aliniih ni ch rank-g ihegsesen bolno!
}

За одоо MST бодлогоруугаа буцаад ороё. Тэгэхээр орой бүрийг мод гэж үзээд ирмэгүүдийг орж ирэх бүрт ирмэгийн хоёр оройг нэг ойд байгаа эсэхийг шалгана. Мэдээж хоёр орой хоёр тусдаа ойд байрлаж байвал Merge хийхнээээ.

Union Find-g маш сайн анализ хийгээд өөрөө бичээрэй. Нээрээ Рекурсыг бүр сайн ашиглаж ойлгоё гэвэл хаалттай, зэрэг, үржих хуваах үйлдэл хийдэг илэрхийлэл ( 12*32/32+(32+32)*32/2 ) шууд боддог код, мөн Хоёртын хайлтын мод, Тэнцвэрт хоёртын хайлтын модыг өөрөө бичвэл тэгээд рекурс ОК гэсэн үг шүү хэхэ.
Доор MST олох C++ код тавьлаа, даалгавар болгоход MST модыг өөрийг бүх оройн хамт хэвлэнэ үү. Нэмж сонирхуулахад өгөгдсөн графын бүх ирмэгүүдийн жин ялгаатай бол MST цор ганц оршино. Энийг индукцээр баталчиж болно. Тэгвэл зарим нэг ирмэг нь ижил жинтэй бол MST хэд байж болох вэ? гээд дахиад өөр бодлого гарж ирнэ. Энэ хамаагүй хүнд бодлого болчиж байгаан. Мөн чиглэл графын хувьд бас MST олох бодлогыг дараа тайлбарланаа.
//Task: MST http://www.spoj.pl/problems/MST/
//O(E log V)
//written: Dunno
#include<cstdio>
#include<cstdlib>
#include<utility>
#include<vector>
#include<algorithm>
using namespace std;
#define edge pair<int,int>
#define Max 100001
// w(u,v)
vector< pair<int,edge> >Mst,G;
long Parent[Max];
long Rank[Max];
void MakeSet(long x){
Parent[x] = x ;
Rank[x] = 0 ;
}
long Find(long x){
if(x!=Parent[x])
Parent[x]=Find(Parent[x]);
return Parent[x];
}
void Merge(long x, long y){
long Px = Find(x);
long Py = Find(y);
if (Rank[Px]>Rank[Py])
Parent[Py]=Px;
else Parent[Px]=Py;
if(Rank[Px]==Rank[Py])++Rank[Py];
}
int main(){
//freopen("kruskali.txt","r",stdin);
//freopen("kruskalo.txt","w",stdout);
long n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)MakeSet(i);
for(int i=1; i<=m; i++){
int v,u,w;
scanf("%d%d%d",&v,&u,&w);
G.push_back(pair< int,edge >(w, edge(v,u)));
}
sort(G.begin(),G.end());
/*for(int i = 0; i<m;i++){
printf("w = %d, v = %d, u = %d\n",G[i].first,G[i].second.first,G[i].second.second);
}*/
long long res = 0LL ;
long k = 0;
for(int i=0;i<m;i++){
long Pv = Find(G[i].second.first);
long Pu = Find(G[i].second.second);
if(Pu!=Pv){
Mst.push_back(G[i]);
res += G[i].first;
Merge(Pu,Pv);
k++;
}
if(k==n-1)break;
}
printf("%lld\n",res);
return 0;}


Python дээрх код:

class DisjointSet(dict):
def add(self, item):
self[item] = item

def find(self, item):
parent = self[item]

while self[parent] != parent:
parent = self[parent]

self[item] = parent
return parent

def union(self, item1, item2):
self[item2] = self[item1]
from operator import itemgetter

def kruskal( nodes, edges ):
forest = DisjointSet()
mst = []
for n in nodes:
forest.add( n )

sz = len(nodes) - 1

for e in sorted( edges, key=itemgetter( 2 ) ):
n1, n2, _ = e
t1 = forest.find(n1)
t2 = forest.find(n2)
if t1 != t2:
mst.append(e)
sz -= 1
if sz == 0:
return mst

forest.union(t1, t2)


#test

nodes = list( "ABCDEFG" )
edges = [ ("A", "B", 7), ("A", "D", 5),
("B", "C", 8), ("B", "D", 9), ("B", "E", 7),
("C", "E", 5),
("D", "E", 15), ("D", "F", 6),
("E", "F", 8), ("E", "G", 9),
("F", "G", 11)]

print kruskal( nodes, edges )
#output: [('A', 'D', 5), ('C', 'E', 5), ('D', 'F', 6), ('A', 'B', 7), ('B', 'E', 7), ('E', 'G', 9)]


Зурагнуудыг Тодкодер болон Вики-гээс авлаа.

UPDATE:

Union Find-аар энэ бодлогыг бодно уу.

Дараагийн постууд:

- Articulation Point

- Divide and Conquer

- IOI 2011 Race бодлогын бодолт

- Bipartite Matching

- Tarjan

- Flow

Tuesday, May 1, 2012

Бидний гаргасан амжилтууд (Team SMCS1)

Саяхан Улсын програмчлалын 22-р олимпиад Монгол Улсын Их Сургуулийн Мэдээллийн Технологийн Сургууль дээр амжилттай явагдаж өнгөрснийг та бүхэн мэдэж байгаа байх. Энэ удаад гэхдээ үүнийг бичих гэсэнгүй. SMCS1 гэдэг баг 4 жил болж нэг бүтэн үе өнгөрч байгаа болохоор та бүхэнтэй дурсамжаа хуваалцая гэсэн юм.

2008-2009 он
Би энэ сургуульд 2008 онд 16 настайдаа элсэн орсон. Ингээд сургуульд ороод хамгийн анх танилцсан хүн бол Багаболд багш. Тухайн үед coder.mn дээр бодлого боддог байсан болохоор магадгүй багш минь намайг анзаарч харсан байх. Гэхдээ одоо бодоход тэр үед манай улсын түвшин хамаагүй доогуур байжээ :). Ингээд энэ хүн сургуулиасаа сонирхлоороо нэгдсэн оюутнуудыг цуглуулан манай анхны баг бүрдсэн юм. Ангийн найз болох Мөнх-Очир, мөн дээд курсын ах Хасан нар юм. Ингээд л бид 3-н хамтарсан бэлтгэл эхлэсэн юм. Тухайн үед coder.mn, spoj.pl/CSMS сайтууд хамгийн алдартай нь байлаа. Энэ сайтуудын тэмцээн уралдаан, бодлогыг чадах чинээгээрээ оролдож B@Ts, Irmuun ах нараасаа ч их юм сурсан даа. Мөн үүгээр зогсохгүй багшийнхаа зөвлөснөөр олон улсын бодлогын томоохон архив UVa сайт дээр эрчимтэй бэлтгэл хийж эхлэсэн юм. Тэр үед хичээл номоо ч анхааралгүй үнэхээр их боддог байсан, дэндүү дуртай байсан гэх юм уу даа. Бид бие рүү шөнө 2, 3 цаг гэхэд утасдаж сэрээгээд хамтдаа Yahoo Messenger дээр conference үүсгэж байгаад 5 цагийн тэмцээнд ороод өглөөнөө буцаад унтдаг байлаа :D.
Ингээд бэлтгэлийн үр дүнгээ шалгах хамгийн эхний тэмцээн нь Улсын програмчлалын 19-р олимпиад байсан юм. Уг олимпиадад санасныг бодвол тааруухан оролцсон. Би мөнгөн медал, Мөнх-Очир хүрэл медал авсан юм. Энэ жил бол миний хамгийн эхний азгүйтэл байсан :P, pascal хэлний санах ойгоос болж 1 бодлогоо 0-уулсан юм хэхэ. Багийн нийлбэр дүнгээр КТМС-н багтай тэнцэж медалийн чанараар КТМС түрүүлсэн.
Дараагийн тэмцээн нь үндэсний програмчлалын анхны олимпиад байлаа. Энэ олимпиад бүх хүнд цоо шинэ байсан, 3 хүн хамтдаа 1 компъютер дээр сууж боддог. Ингээд энэ тэмцээнд МТС-н багтай оноо тэнцэж хугацаагаараа ялагдан 2-р байранд орлоо. Гэхдээ намар Шанхай хотноо зохиогдох Азийн бүсийн тэмцээнд оролцох эрхээ авсан юм.

2009-2010 он
Ингээд 2009 оны намар Хасан, Мөнх-Очир бид 3 Багаболд багшийнхаа хамтаар Азийн бүсийн 34-р олимпиадад оролцохоор явсан. Бид Монгол улсаас анх удаа хүрэл медал хүртжээ. Энэ бол дөнгөж эхлэж байсан бидний хувьд гайхалтай амжилт байсан юм.
Ингээд дараагийн оюутны бүсийн програмчлалын олимпиадад явахад Хасан ах маань 4-р курс байсан тул дараагийн гишүүнээ сонгохоор болж Алмабек манай багийн гишүүн боллоо. Алмабек маань бидэнтэй 1-р курсээс л цуг явж, цуг бэлтгэл хийсэн их хөдөлмөрч байсан. Гэвч улсын олимпиадад Хасан ахынхаа хамтаар орох нь ойлгомжтой :).
Улсын програмчлалын 20-р олимпиадад Бид гайхалтай амжилт үзүүлж 1, 2-р байрыг хол тасархай эзлэсэн юм. Би алтан медаль, Хасан мөнгөн медаль (3-р байрнаас тасархай байсан санагдаж байна). Ингээд нийлбэр дүнгээр МКС маань түрүүлж шилжин явах цомыг хүртлээ.
Програмчлалын үндэсний 2-р олимпиадад Хонгор, Мөнх-Очир, Алмабек нарын бүрэлдэхүүнтэй SMCS1 баг маань илт давуу ялалт байгуулан 1-р байр эзэлж намар БНХАУ-н Тянжин хотноо зохиогдсон тэмцээнд оролцох эрхтэй болсон юм.

2010-2011 он
Мөн 2010 оны намар бид багшийнхаа хамтаар Азийн бүсийн 35-р олимпиадад оролцож өмнөх жилийн амжилтаа давтан хүрэл медалийн эзэд боллоо. Гэхдээ бидэнд энэ амжилт чамлалттай санагдсан юм.
Ингээд мөн л Алака маань 4-р курс болсон тул дараагийн гишүүнээ хайж Баскатайгаа нэгдсэн юм. Баска бид 2 coder.mn дээр цуг бодлого боддог их сургуульд орохоос ч өмнө бие биенээ таньдаг байсан.
Улсын програмчлалын 21-р олимпиадад Алмабектэй цуг орсон юм. Энэ олимпиадад манайх дундаж амжилт үзүүлэн Мөнх-Очир бид 2 мөнгөн медалийн эзэд болж МТС цомын эзэд болсон. Энэ олимпиад бас л асуудалтай болсон хэхэ, тэр тухай post хийж байсан. Гэхдээ тэр нь би түрүүлэх байсан гэсэн үг биш л дээ, Өсөхбаатарыг би хувьдаа их үнэлдэг.
Програмчалын үндэсний 3-р олимпиадад Хонгор, Мөнх-Очир, Баска нар орсон юм. Хамгийн гайхалтай нь Хасан ах маань нэгэнт сургуулиа төгсөж ажилд ороод уг олимпиадад комиссын гишүүн болж ажилласан юм :). Бид энэ тэмцээнд мөн л гайхалтай ялалт байгуулан Далианд оролцох эрх авсан.

2011-2012 он
Сая намар буюу 9 сард Баска, ЛМО бид 3 Азиийн бүсийн 36-р олимпиадад оролцож үнэхээр гайхалтай оролцон мөнгөн медалийн эзэд болсон юм. Одоогоор манай багаас өөр баг ямар нэгэн медал аваагүй байна :).
Ингээд Баска маань Америк сурахаар явж, Мөнх-Очир бид 2 төгсөх курс болсон учраас програмчлалын үндэсний 4-р олимпиадад оролцоогүй юм.
Улсын програмчлалын 22-р олимпиад саяхан болж өнгөрснийг та бүхэн мэдэж байгаа байх. Мөнх-Очирын маань хувьд эхний өдөр ялихгүй алдаа гаргаж хамаг оноого алдсан ч удаах өдөр нь гайхалтай оролцон 150 оноо авснаар мөнгөн медалийн эзэн болсон юм.
Харин миний хувьд эхний өдөр түрүүлсэн. Гэвч бас л нэгэн азгүйтэл ч юм уу нийлбэр дүнгээр мөнгөн медал авсан юм. Энэ жил түрүүлэхийг их хүсч байлаа, угаасаа цаанаасаа л болдоггүй юм болдоггүй юм шиг байна. Тэр өдөр миний гаргасан авирт зарим хүн дургүйцэж магадгүй, гэхдээ над шиг 2 удаа түрүүлчихээд 2-т орно гэдэг хэцүү л болов уу.

Ингээд гаргасан амжилтуудаа жагсааж бичвэл:

Э.Хонгор:
Програмчлалын үндэсний олимпиадын 2 алт, 1 мөнгөн медал.
Улсын програмчлалын олимпиадын 1 алт, 3 мөнгөн медал.
Азийн бүсийн олимпиадын 1 мөнгөн, 2 хүрэл медал.
ТопКодер-н 2011 оны нээлттэй тэмцээний шилдэг 350 оролцогчийн нэг.

Б.Хасан:
Програмчлалын үндэсний олимпиадын 1 мөнгөн медал.
Улсын програмчлалын олимпиадын 1 мөнгөн медал.
Азийн бүсийн олимпиадын 1 хүрэл медал.

Л.Мөнх-Очир:
Програмчлалын үндэсний олимпиадын 2 алт, 1 мөнгөн медал.
Улсын програмчлалын олимпиадын 2 мөнгө, 1 хүрэл медал.
Азийн бүсийн олимпиадын 1 мөнгө, 2 хүрэл медал.

Н.Алмабек:
Програмчлалын үндэсний олимпиадын 1 алтан медал.
Азийн бүсийн олимпиадын 1 хүрэл медал.

П.Баасанбат:
Програмчлалын үндэсний олимпиадын 1 алтан медал.
Азийн бүсийн олимпиадын 1 мөнгөн медал.

Одоо бид:
Б.Хасан, Л.Мөнх-Очир, Э.Хонгор нар Мобиком корпорацид програмистаар хамт ажиллаж байна.
Н.Алмабек GrapeCity компанид програмистаар ажиллаж байна.
П.Баасанбат Америкт суралцаж байна.


Ингээд харахад бид чамлахааргүй их амжилтыг гаргажээ. Энэ хүртэл надтай хамт байсан Хасан ахдаа, ЛМОдоо, Баскадаа, Алакадаа бүгдэнд нь баярлалаа. ХАмгийн их баярлалаа гэж хэлэх хүн маань Багаболд багш юм. Энэ хүн байгаагүй бол өдийд энэ их амжилт байхгүй байх байсан. МКС-н маань амжилтын буухиа тасрахгүй гэдэгт итгэж байна.

Tuesday, April 10, 2012

Рекурсив хамаарал, Матриц ашиглан N-р гишүүнийг Log-д олох

Бид рекуррент харьцаатай бодлого бодож байхдаа N-аар гишүүнийг DP ашиглан O(N)-д олдог. Гэвч зарим тохиолдолд хугацааны хязгаарлалт маш бага байхад 10^6-р гишүүнийг P модуль жиш гэсэн бодлогууд гарч ирдэг. Жишээ нь Фибаночийн дарааллын N-р гишүүнийг (1<=N<=1000000000) % 999983 мөн саяны Үндэсний олимпиадад бодогдсон Маяабоначийн тоо бодлого.



Энэ постыг та уншихаасаа өмнө Матрицын талаар зохих мэдлэгтэй, үржих, нэмэх хасах үйлдэлүүдийг хийж чаддаг байх ёстой шүү!



Ерөнхий тохиолдол:

Бид өгөгдсөн эсвэл өөрийн олсон рекуррент харьцаагаа ашиглан дараах шинэ харьцааг гаргаж авна.
X(i+1) = M*X(i) Энд X нь Кх1 хэмжээтэй матриц, М нь КхК хэмжээтэй тогтмол утгатай матриц. Бид тэгэхээр рекуррэнт харьцаагаа ашиглан М матрицыг олох ёстой.



  X(i+1)              X(i)
| f(n+1) | | f(n) |
| f(n) | | f(n-1) |
| f(n-1) | = M x | f(n-2) |
| ...... | | ...... |
| f(n-k+1) | | f(n-k) |






Бодлогуудыг ерөнхийд нь дараах хэдэн төрөлд хувааж болно.



#1::

Хамгийн энгийн буюу рекуррент харьцаа дараах хэлбэрийнх байж болно.

f(n) = f(n-1) + f(n-2). Үүнийг дараах байдлаар бичиж болно. f(n+1) = f(n) + f(n-1). Тэгвэл X(i+1) болон X(i) матрицууд
дараах байдлаар бичигдэнэ.



  X(i+1)           X(i)
| f(n+1) | = M x | f(n) | => | f(n+1) | = | a b | x | f(n) |
| f(n) | | f(n-1) | | f(n) | | c d | | f(n-1) |


Тэгэхээр харж байгаа байх, К-г олохдоо тухайн харьцаа өөрөөсөө өмнөх хэдэн гишүүнээсээ хамаарч байна, тэдгээрийн гишүүдийн тоотой тэнцүү байх нь. Фибаночийн хувьд өмнөх 2 гишүүнээсээ хамаардаг тул бидний матрицууд X(Kx1), M(KxK) байх нь. Одоо М матрицыг олъё. Дээрх тэгшитгэлийн баруун талын 2 матрицыг үржүүлээд бодохоор бид дараах 2 тэгшитгэлийг гаргаж чадна.

a*f(n) + b*f(n-1) = f(n+1)

c*f(n) + d*f(n-1) = f(n) . Дарааллыхаа эхний 3-н гишүүнийг мэдэх учир орлуулаад бодохоор a=1, b=1, c=1, d=0 гэж гарна.

Одоо бид М-г мэдэх учраас дараах байдлаар бичиж болно.

| f(n+1) | = | 1 1 | x | f(n)   |
| f(n) | | 1 0 | | f(n-1) |




#2::

Рекуррент харьцаа дараах хэлбэртэй байж болно. f(n) = a * f(n-1) + b * f(n-2). a, b тогтмол тоо. Дараах хэлбэртэй тэнцүү f(n+1) = a * f(n) + b * f(n-1).

Бидний олох матрицууд К нь 2 гэдгийг хялбархан харж байгаа байх. Өмнөхтэй адилхан энгийн алгебр бодоод дараах харьцаагаа гаргана.

| a   b | x |  f(n)  | = | f(n+1) |
| 1 0 | | f(n-1) | | f(n) |




#3::

f(n) = a * f(n-1) + c * f(n-3). Энд f(n-2) байхгүй үед яах вэ? Энэ дараах формтой тэнцүү.

f(n) = a * f(n-1) + 0 * f(n-2) + c * f(n-3) = f(n+1) = a * f(n) + 0 * f(n-1) + c * f(n-2)



| a  0  c |   |  f(n)  |   | f(n+1) |
| 1 0 0 | x | f(n-1) | = | f(n) |
| 0 1 0 | | f(n-2) | | f(n-1) |.




#4::

f(n) = a*f(n-1) + b*f(n-2) + c, С дурын тогтмол тоо.



| f(n+1) | = | a b 1 | x | f(n)   |
| f(n) | | 1 0 0 | | f(n-1) |
| c | | 0 0 1 | | c |




#5::

Зарим тохиолдолд И тэгш үед ийм, сондгой үед ийм гэхчихлэн өөр өөр нөхцөлтэй байдаг. Энэ үед аль ч тохиолдолд нь 2 өөрөөр бодолт хий.

#6::

Рекуррент харьцаа 2 өөр функцээр тодорхойлогдож болно.

g(n) = a*g(n-1) + b*g(n-2) + c*f(n), f(n) = d*f(n-1) + e*f(n-2).



| g(n+1) |   | a b c 0 |   | g(n)   |
| g(n) | = | 1 0 0 0 | x | g(n-1) |
| f(n+2) | | 0 0 d e | | f(n+1) |
| f(n+1) | | 0 0 1 0 | | f(n) |




За ерөнхийдөө ойлгомжтой гэж бодож байна. Ингээд М матрицаа олцон тохиодолд яах вэ?

X(i+1) = M*X(i). Хоёр талыг хоюуланг нь М-д үрж.



M * X(i+1) = M * M * X(i)

X(i+2) = M^2 X(i)

..

X(i+k) = M^k * X(i)

Бид 2 матрицыг O(N^3)-д үрждэг. Тэгвэл К зэргийг шугаман олвол O(N^3*K). Хэрвээ бид К зэргийг Log-д олж чадвал O(N^3*LogK) хангалттай хурдан олж чадахнээ. Одоо зэргийг хэрхэн Log-д олохыг харъя.
a^k олох байг.

- Хэрэв К 0 бол мэдээж 1

- Хэрэв К тэгш тоо байвал a^k = (a(k/2)*a(k/2))

- Хэрэв К сондгой байвал a^k = (a(k-1)*a)

Үүнийг С++ дээр бичвэл:

int p(int n, int k){
int ret = 1;
if(k==0)return ret;
if(k==1)return n;
if(k%2==0) ret *= p(n, k/2)*p(n, k/2);
else ret *= p(n, k/2)*p(n, k/2)*n;
return ret;
}



Маяабоначийн Тоо бодлого бол дээрх формоос 3дугаар форм-д нь орох бодлого. М матрицаа ерөнхийдөө аналитик байдлаар гаргана. М матрицын эхний мөрний a болон b дэх багана дахь тоо 1 бусад нь 0. Бусад мөрийн (i-1)-р тоо нь 1 бусад нь 0 гэж матрицаа үүсгэнэ. Бодолт O(B^3 * LogN)-д шийдэгдэнэ.