Skip to main content

BFS-Түвшний нэвтрэлт

Түвшний нэвтрэлт(BFS-Breadth first search) гэх алгоритмын тухай бичих болно. Жишээ бодлого дээр ярилцъя. Бидэнд нэг граф өгөгдсөн байгаа гэж бодъё. Тэгээд нэг хүсэлт байгаа тэр нь хоёр орой өгөгдөх ба энэ хоёр оройн хооронд хамгийн багадаа хэдэн ширхэг оройгоор дамжин очих вэ? гэдгийг олох юм. Бид одоо X гэсэн орой дээр байгаа. Y орой хүрэх хамгийн богино замыг олцгооё. Бид X орой дээр 0 зам туулж ирсэн гэж үзэж болох юм. Мөн энэ оройг 0-р түвшин гэж үзье. Тэгвэл 1 гэсэн түвшинтэй орой гэж ямар оройг хэлэх вэ? Энэ оройнууд бол X оройтой шууд ирмэгээр холбогдсон оройнууд юм. Тэгвэл 1-р түвшинтэй орой дээр 1 гэсэн зам туулж ирнэ. Тэгвэл 2-р түвшин гэдэг нь 1-р түвшний оройтой шууд ирмэгээр холбогдсон оройнууд байх юм. Энэд нэг анхаарах зүйл байгаа нь 1-р түвшинтэй оройнуудтай 0-р түвшний орой холбогдсон байгаа ба бид энэ оройг 2-р түвшин гэж хэлэх нь буруу юм. Тиймээс өмнө нь ирээгүй орой буюу шинэ оройд л түвшний утга өгөх нь зүйтэй. Энэ мэтчилэн бид X орой буюу 0-р түвшнээс Y орой таартал түвшинг нэмсээр байх болно. Ингээд Y орой дээр ирэх үед энэ түвшин нь X оройгоос Y орой хүртэлх хамгийн бага замын урт байх юм. Энгийнээр төсөөлбөл бид графын ирмэгүүдийг хоолой гэж төсөөлье. Тэгвэл бид X оройгоос ус асгасан гэж үзье. Энэ ус нь эхлээд X оройгоос гарсан бүх хоолойгоор урсана. Дараа нь тэр оройнуудаас гарсан хоолойнуудаар урсана. Ямар нэг орой дээр ус анх удаа ирж байгаа бол X оройгоос ирж болох хамгийн бага зам туулж ирсэн гэсэн үг. Харин анх удаа биш бол энэ орой дээр ус аль хэдийнээ ирж амжсан тул энэ явалт нь ашиггүй. Гол санаа нь нэг нэг түвшнээр ахиж байгаад оршино. Кодыг нь хэрхэн бичих вэ? Бид queue гэсэн бүтцийг ашиглан бичнэ(Яагаад vector эсвэл өөр бүтэц биш вэ?). Queue нь хос байх ба эхнийх нь одоо явж байгаа оройг харин дараагийнх нь одоо явж байгаа түвшинг авна. Queue-д хамгийн эхлээд X, 0 гэсэн хосыг нэмж өгнө. Бид queue-ын хамгийн эхний элементийг авах буюу X, 0 гэсэн хосыг авна. Энэ нь X орой дээр хамгийн багадаа 0 гэсэн утгаар ирж чадсан гэж үзэж байгаа тул X оройг ирсэн гэж тэмдэглэнэ. Дахиж энэ оройгоос явах шаардлагагүй(ус урсгах шаардлагагүй. Усаа хэмнэ). Харин дараа нь бид X оройтой шууд ирмэгээр холбогдсон бүх оройг queue-д нэмж өгөх ба нэмэхдээ эхний утга нь тэр орой харин хоёр дахь утга нь 1 байх болно. Учир нь энэ оройнуудын түвшин 1. Ингээд энэ оройгоос бүгд урсаж чадсан. Дараагийн түвшинлүү орох ёстой тул үүнийг queue-ээс хасах хэрэгтэй. Харин дараа нь давталт эхнээсээ эхэлнэ. 1-р түвшинтэй хамгийн эхний орой гарж ирнэ. Энэ оройтой шууд холбогдсон өмнө нь очоогүй бүх оройг 2 гэсэн утгатай хос болгон queue-д нэмнэ. Тэгээд үүнийг хасаад. Дараагийн 1-р түвшний оройруу шилжинэ. Гэх мэт Y оройтой таартал явах юм. Энэ алгоритм нь өгөгдсөн оройгоос бүх холбоотой орой хүртэлх хамгийн богино замыг олж байгаа юм. Доорх код нь энэ хаяг дээр байгаа болно.

  1. //Түвшний нэвтрэлт. BFS  
  2. #include <queue>  
  3. #include <iostream>  
  4. using namespace std;  
  5.       
  6.     int n, m;   // оройн тоо, ирмэгийн тоо  
  7.     bool vis[50000];    // ирсэн ирээгүйг хадгалах массив.  
  8.     vector<int> adj[50000];   // графын мэдээллийг хадгалах вектор.  
  9.     queue< pair<int,int> > q;   // bfs явах queue  
  10.   
  11. int main() {  
  12.     cin >> n >> m;  // орой, ирмэгийн тоонуудыг унших.  
  13.   
  14.     for(int i = 1; i <= m; i++) {  
  15.         int u, v;   // i-р ирмэгийг тодорхойлох 2 орой.  
  16.         // u болон v орой холбогддог гэсэн утгатай тул  
  17.         cin >> u >> v;  
  18.         adj[u].push_back( v );  
  19.         adj[v].push_back( u );  
  20.     }  
  21.     int x, y;   // x-ээс y хүртэл  
  22.       
  23.     cin >> x >> y;  // хүсэлтээ унших  
  24.   
  25.     q.push( make_pair( x, 0 ) );    // анх x оройдээр 0 утгаар  
  26.   
  27.     while( !q.empty() ) {   // хоосон биш л бол үргэлжилнэ.  
  28.         int nowU = q.front().first; // одоо явж байгаа орой.  
  29.         int nowW = q.front().second;    // одоо явж байгаа оройн түвшин  
  30.           
  31.         vis[nowU] = 1;  // nowU оройг ирсэн гэж тэмдэглэнэ.  
  32.   
  33.         if( nowU == y ) {  
  34.             // nowU орой нь бидний хайсан Y орой бол энэ хүрээд зогсоно.  
  35.             cout << nowW << endl;   // хамгйин бага зардал.  
  36.             return 0;   // программыг зогсоож болно гэсэн үг юм.  
  37.         }  
  38.   
  39.         q.pop();    // дараагийн элемэнтээс явах ёстой тул үүнийг устгана.  
  40.   
  41.         for(int i = 0; i < adj[nowU].size(); i++) {  
  42.             int v = adj[nowU][i];   // одоо байгаа оройтой шууд ирмэгээр холбогдсон орой.  
  43.             if( !vis[v] ) { // v орой тэмдэглэгдээгүй бол очиж ёстой.  
  44.                 q.push( make_pair( v, nowW+1 ) );   // энэ оройдээр одоо байгаа  
  45.                                                     // түвшиндээр нэмэх нь 1-ээр ирнэ.  
  46.             }  
  47.         }  
  48.     }  
  49.     cout << "Ochih bolomjgui!\n";     // хэрвээ бүрэн зогсоогүй бол очиж   
  50.                                     // чадаагүй буюу очих боломжгүй гэсэн үг.  
  51.     return 0;  
  52. }  

Comments

Popular posts from this blog

Тооны зэрэг

Юм хийхгүй байсан ч хугацаа өнгөрдөг юм байна. Дахиад a^b%p буюу a тооны b зэргийг p гэсэн тоонд хуваахад гарах үлдэгдлийг олцгооё(p нь анхны тоо). Өмнө нь бид a, b <= 10^13 гэсэн тохиолдолд бодсон байгаа. Энэ удаад үүнийгээ өргөтгөн a, b гэсэн хоёр тооны оронгийн тоо нь 10^6-с бага буюу тэнцүү тохиолдолд бодоцгооё(бодъё гэдэг үг бичих гэж байсан ч яаж бичих дээр эргэлзсэн ба p long long төрлийх). fermat -н бага  теоремоор  (a^(p-1))%p = 1. Үүнийг ашиглахад энэхүү бодлого маш амархан болж байгаа. (a^b)%p = ((a%p)^b)%p тул эхлээд a тоог p-д хуваасан үлдэгдлийг олцгооё. a тоо маш олон оронтой байж болох тул бид string ашиглан уншина. Энэ тооны оронгийн тоо нь s = a.size() гэвэл a = a[s-1]*(10^0)+a[s-2]*(10^1)+a[s-3]*(10^2)+...+a[0]*(10^s-1) байх ба a%p = (a[s-1]*(10^0)%p+a[s-2]*(10^1)%p+a[s-3]*(10^2)%p+...+a[0]*(10^s-1)%p)%p байна. Одоо b гэдэг тоог харцгаая. b = k*(p-1)+l байдаг гэе(l<p-1). Тэгвэл a^b%p = (a^(k*(p-1)+l)%p = a^(k*(p-1))*a^l%p эндээс нөгөөх  теорем  ёсоор (a^(p-1))%

Массив

Массивын тухай. У рьдын адил энэ хаяг дээр байгаа. Массив гэдэг бараг л олонлог гэсэн үг юм. int a[2000]  гэдэг нь a гэсэн нэртэй олонлог 2000 ширхэг тоо агуулах боломжтой гэсэн үг юм. Массивын эхний элемэнтын дугаар гэдэг бол 0 юм.            Жишээ нь:            5            5 4 -3 2 9            гэсэн оролт бол 5 ширхэг тоо орж ирэх ба эхний мөрөнд массивын хэмжээ буюу массив нь 5 ширхэг элемэнт агуулна. Дараагийн мөрөндөх тоонууд нь олонлогийн элемэнтүүд юм.            a[0] гэдэг бол 5            a[1] гэдэг бол 4            a[2] гэдэг бол -3            a[3] гэдэг бол 2            a[4] гэдэг бол 9 болох юм. // Энэ удаад массивын тухай үзэх болно.    #include <cstdio> // stdio.h гэсэнтэй ижил болно.        int  main() {        // Бодлого. орлтонд N, K болон N ширхэг бүхэл тоонууд өгөгдөх ба эдгээр тоонуудын         // нийлбэрийг ол. Дарагийн мөрөнд K-р элемэнтийг хэвлнэ. K < N && 1 <= N <= 1000.         int  n;  // массивын хэмжээ.         int  

Тэмдэгт мөр(char)

Mэмдэгт мөрийн тухай. Энэ нь тэмдэгтүүдийн  массив гэсэн үг юм. Өөрөөр хэлбэл char a[2000] гэсэн a нэртэй массив нь 2000 ширхэг тэмдэгтийг авж чадна гэсэн утгатай юм. Тэгээд энэ линк дээр байгаа.        Жишээ оролт.        !-01Xc@        Энэ орлтын хувьд        a[0] = '!' буюу 0р тэмдэгт нь ! тэмдэг        a[1] = '-' буюу 1р тэмдэгт нь - тэмдэг        a[2] = '0' буюу 2р тэмдэгт нь 0 тэмдэг        a[3] = '1' буюу 3р тэмдэгт нь 1 тэмдэг        a[4] = 'X' буюу 4р тэмдэгт нь X тэмдэг        a[5] = 'c' буюу 5р тэмдэгт нь c тэмдэг        a[6] = '@' буюу 6р тэмдэгт нь @ тэмдэг болох юм. // Энэ удаад тэмдэг мөр үзэх болно.    #include <cstdio>    #include <cstring> // тэмдэгт мөрдээр хийгдэх үйлдлүүдийг ашиглахыг хүсвэл энэ санг заавал                        // зарлаж өгөх ёстой. Жишээ нь тэмдэгт мөрийн урт гэх мэт функцийг агуулдаг.        int  main() {        // Бодлого. 1 тэмдэгт мөр өгөхдөх ба энэ тэмдэгт мөр

while давталт.

Happy valentine's day. while давталтын тухай.  Уншиж байгаа бүхнийгээ өөрийн болгох нь хамгийн чухал болхоор олон бодлого бодож байгаарай. Доорх кодыг урьдын адил энэ сайтад байрласан байгаа. Энэд бичсэн өгөгдсөн тооны цифрийн нийлбэрийг олох бодлогын санаа бол өгөгдсөн тооны хамгийн сүүлийн цифрийг олоод нэг хувьсагчид утгыг нэмнэ. Дараа нь энэ цифрийн урд талын цифрийг нэмнэ. Дараа нь тэрний урд талын гэх мэт гэхдээ энийг яаж нэмэх вэ? гэвэл бид хамгийн ард талын цифрийг олж чадна. Энэ нь тэр тоог 10д хуваасан үлдэгдэл юм. Харин тэрний урд талын цифрийг яаж олох вэ? гэвэл тэр тоог 10д хуваагаад гарсан хариуг 10д хуваасан үлдэгдэл юм. Гэх мэтчилэн энэ тоо 0-ээс их бол энэ үйлдлүүдийг үргэлжлүүлээд л хийгээд байх болно. Үүнийг while давталт ашиглана. Жишээ нь : 142 гэсэн тооны хувьд бодоцгооё.  Хамгийн арын цифр 2 энэ тоог хариунд нэмээд 10д хуваана. Одоо энэ тоо 14 болно. Хариу нь 2 болно. Хамгийн арын цифр 4 энэ тоог хариунд нэмээд 10д хуваана. Одоо энэ тоо 1 болно. Хариу н

Нөхцөл шалгах үйлдэл.

Нөхцөл шалгах үйлдэл буюу if гэсэн зүйлийг үзнэ. Тайлбар хийсэн кодон дээр 3 бодлого цуг байгаа тул жоохон эвгүй байж магадгүй.(Залхуурсан болно). Дахин сануулахад энэ сайтаас бодоод яваарай. Яг бидний үзэж байгаа бодлогууд эхний хэдэн хуудсан байгаа. Доорх код энэ сайтад байгаа. Нэмж хэлэхэд ideone бол онлайн compiler бөгөөд заавал компьютэр дээрээ devc++ эсвэл өөр compiler суулгахгүйгээр энэ сайтыг ашиглаж болно. && - And буюу ба. || - Or буюу эсвэл. // Энэ удаад нөхцөл шалгах үйлдэл буюу if гэсэн нөхцлийг үзэх болно    // Мөн &&, || гэсэн логик оператор үзэх болно.    #include <stdio.h>        int  main() {       printf( "Bodlogo 1\n" );        // Бодлого 1: 2 бүхэл тооны багыг ол.         // Бодолт: Ямартай ч бид 2 бүхэл тоогоо унших ёстой. үүний тулд          // ашиглах хувьсагчдаа зарлах ёстой.         int  a, b;           scanf( "%d%d" , &a, &b);  // Бидний мэдэх унших үйлдэл             // Хэрвээ а тоо b 

Sieve

Sieve of Eratosthenes  тухай. N тоог анхны тоо эсэхийг sqrt(N) үйлдлээр шалгаж болно. Тэгвэл 1-с N хүртэлх бүх анхны тоог ол гэсэн бол үүнийг ашиглавал ойролцоогоор N*sqrt(N) үйлдлээр хийх юм. Одоо бичих алгоритм нь илүү хурдан юм. Анхны тоо гэдэг нь зөвхөн 2 ширхэг натурал тоон хуваагчтай тоо ба өөрөөр хэлбэр анхны тоо нь өмнөх ямар ч анхны тоондоо хуваагддаггүй гэсэн үг. 2-г хамгийн эхний анхны тоо гэдгийг мэднэ. Тэгвэл 2-д хуваагддаг дараагийн бүх тоонууд анхны тоо болж чадахгүй. Тэгвэл энэхүү тоонуудыг анхны тоо биш гэж тэмдэглэе. Дараагийн тоо нь 3 ба энэ нь өмнөх бүх анхны тоондоо хуваагдаагүй тул энэ тоо нь анхны тоо. Тэгвэл 3-д хуваагддаг дараагийн бүх тоонууд анхны тоо биш. Дараагийн тоо нь 4 ба энэ нь 2-д хуваагддаг тул анхны тоо биш гэж тэмдэглэгдсэн. Дараагийн тоо нь 5 ба энэ нь  анхы  тоо биш гэж тэмдэглэгдээгүй ба өмнөх бүх анхны тоондоо  хуваагдаагүй  тул анхны тоо. Тэгвэл 5-д хуваагддаг дараачийн тоонууд нь анхны тоо биш болох юм. Энэ мэтчилэн бодвол хугацааны үнэлг

for давталт

for давталтын тухай.  Доорх код энэд байгаа. Энэ сайт бол анхан шатнаас нь эхэлж байгаа хүмүүст бол хамгийн зохимжтой сайт юм. for( хувьсагчийн анхын утга; хувьсагчийн биелүүлэх нөхцөл; хувьсагчийн өөрчлөгдөх хэсэг ) {         хэрвээ хувьсагчийн биелүүлэх нөхцөл үнэн байвал энэ {} хаалтан доторх үйлдлүүд хийгдэнэ.         энэ хаалтан доторх үйлдлүүд хийж дууссаны дараа хувьсагч нь өөрчлөгдөнө.     }     Ерөнхий бүтэц бол иймэрхүү. // Энэ удаад for давталтын тухай оруулна.        #include <stdio.h>        int  main() {        // Давталт нь гэдэг нь маш чухал зүйлийн нэг юм. (Энэ нээх чухал өгүүлбэр биш).             // Бодлого. нэг бүхэл тоо өгөхөд 1-ээс тэр тоо хүртэл бүх тоог нэг мөрөнд хэвлээд         // дараагийн мөрөнд 1-ээс тэр тоо хүртэлх 3д хуваагддаг тоонуудын нийлбэрийг ол.             // энэ бодлогыг давталт ашиглаж бодно.         // (Давталт үзэх гэж байж тодорхойшдээ яах гэж бичвээ).             int  n, i, s;         // бид 3д хуваагддаг тоонууды

Тооны зэргийг Log(n) үйлдлээр олох.

Үүгээр чадах бодлого болон алгоритмын анализ эхэлж байгаа ба өмнөх оруулсан зүйлсийг мэдэж байхад энэ бүгдийг ойлгоход асуудал үүсэхгүй байх. A тооны B зэргийг 10^9+7 гэх анхны тоонд модулдаж гарсан хариуг ол. A, B<=10^13. Бид энгийн давталт ашиглан үүнийг олж чадах юм. Гэвч үүнийг олохын тулд B үйлдэл хийх ба энэ нь 10^13 тул маш удаан ажиллана. Үүнийг хэрхэн хурдан хугацаанд шийдэх вэ? B тоо нь хоёртын бичиглэлдээ B=x[0]*(2^0)+x[1]*(2^1)+...+x[k]*(2^k) гэж задардаг байг. Тэгвэл k<=log(10^13) байх юм. Тэгвэл бид A^1, A^2, A^3, ... , A^k гэх мэт k ширхэг тоонуудыг k үйлдлээр байгуулна. Одоо энэ хоёрыг угсарвал A^B=(A^(x[0]*(2^0)))*(A^(x[1]*(2^1)))*...*(A^(x[k]*(2^k))) болох юм. Ингэснээр бид k үйлдлээр A тооны B зэргийг олж чадаж байна. Хэрвээ B тоо нь тэгш бол A^B=(A^(B/2))*(A^(B/2)) харин сондгой үед A^B=(A^(B/2))*(A^(B/2))*A гэдэг нь тодорхой юм. Эндээс бид A тооны B зэргийг мэдэхийг хүсвэл A тооны B/2 зэргийг мэдэж байхад олж чадна гэдэг нь харагдаж байна. Тэгвэл A тооны

String

String-н тухай. Мөн cin, cout. String гэж юу вэ? энэ нь хэмжээгээ өөрчилж чаддаг тэмдэгт төрлийн утга авдаг массив гэж хэлж болно. char a[2000] гэвэл 2000 хүртэлх л тэмдэгтийг агуулж чадна. Харин string a; гэвэл энэ нь өөрийн уртыг өөрчилж чадна. Тэгээд доорхи код энэ хаяг дээр байгаа. Мөн ард нь үг нэмэх мөн 2 тэмдэгтийг ижил эсэхийг шалгах гэх мэт үйлдлүүдэд char-ын массиваас илүү амар юм. Бид char төрлийн массиваар 2 тэмдэгтийг ижил эсэхийг шалгавал индекс бүр дэх үсэг бүрийг ижил эсэхийг шалгана. Харин string төрөлд бол ТэмдэгтМөр1 == ТэмдэгтМөр2 гэхэд л хангалттай. Мөн ард нь элемэнт нэмэхэд  ТэмдэгтМөр = ТэмдэгтМөр + ТэмдэгтМөр1; гэхэд л болно. Гэхдээ char төрөлд ард нь нэмдэг функц байгаа. string a = ""; хоосон болгох a = "aBg"; // a гэдэг тэмдэгт мөр aBg болгож байна. a += "123123"; // 123123 гэсэн тэмдэгт мөрийг нэмж байна. cout << a[0] << endl; // 0р элемэнтийг хэвлэх. // Энэ удаад string гэх зүйлийн талаар үзэх болно.     

Шинэ жил боллоо. Гэсэн ч ахиад л зэрэг.

Шинэ жил боллоо. Гэсэн ч ахиад л зэрэг. Өнөөх л a^b%mod-оо бодоцгооё. Бид хувиргалт хийгээд a, b < mod болгож чадна. Харин mod <= 10^13 гэвэл a^b бодох явцад үржвэр нь хамгийн ихдээ ойролцоогоор 10^26 болох юм. Гэтэл long long төрөлд үүнийг хийж чадах билүү? Тэгхээр өмнөх бодолтууд зөв биш гэдгийг анзаарсан байх. Үүнийг хэрхэн шийдэх вэ? Асуудал нь үржих үйлдэл нь том тооны хувьд асуудалтай байгаа тул үүнийг зөв олох хэрэгтэй. x*(y/2)%mod -г мэдэж байхад бид x*y олж чадна. Өмнө нь ашиглаж байсан санаа буюу a^b олсон бодлоготой адил болж байна.