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 компанид програмистаар ажиллаж байна.
П.Баасанбат Америкт суралцаж байна.


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