تدريب محلل في ياندكس: تحليل مهام الاختبار



مرحبا يا هابر!

ذات مرة ، بعد دراسة كتاب آخر عن علم البيانات سيئ السمعة ، توصلت إلى استنتاج مفاده أن الوقت قد حان لوضع المعرفة المتراكمة موضع التنفيذ ورؤية حياة قسم التحليلات بأم عيني. لحسن الحظ ، أطلقت ياندكس مجموعة مختارة للتدريب لمدة ستة أشهر في الاتجاه المناسب ، ولم أستطع المرور. لقد انتهى قبول طلبات 2020 بالفعل ، لذلك سأقوم في هذه المقالة ، بضمير مرتاح ، بتحليل المهام التي اقترح Yandex حلها للمتقدمين في المرحلة الأولى. سيكون هناك رمز Python. المفسد: صعب ولكنه مثير للاهتمام.

المهمة 1. الموعد النهائي


المهمة


محلل مبتدئ يحاول حل المشكلة. إذا لم يتم حل المشكلة ، فإنه يفقد الدافع ، وينخفض ​​احتمال النجاح في المحاولة التالية. تستغرق محاولة واحدة يومًا ، ويكون الموعد النهائي للمهمة 90 يومًا. احتمال أن يحل المحلل المشكلة من المحاولة الأولى هو:

  1. 1(i+1)
  2. 1(i+1)2

ما مدى احتمال قيام المحلل بحل المشكلة قبل الموعد النهائي؟

القرار


ربما وصلت بالفعل للكتابة: "nice_one ، قلت أنه سيكون من الصعب ، ولكن ما هذا؟" الصبر ، الأصدقاء ، هذه مهمة بسيطة للتدفئة ، ولكن هناك شيء يجب تفويته إذا لم تفكر في الحالة. دعونا نفحص مثال الفقرة الأولى. من الضروري حساب الاحتمالية الإجمالية التي سيحلها المحلل للمشكلة في أي من الـ 90 يومًا المتوفرة في الاحتياطي ، بينما يتم إعطاء احتمال النجاح في كل يوم من الأيام. قد يبدو خيار مغري أن يستبدل في التعبير عددًا من 1 إلى 90 بدلاً من i ويضيف ، لكن هذا ليس صحيحًا. يشير هذا التعبير إلى احتمالية النجاح في يوم محدد ، ولكن من أجل الوصول إلى ذلك اليوم ، يجب أن يفشل المحلل في الأيام الماضية (1 - 1). إذا كان احتمال النجاح في اليوم الأول هو1(i+1)، وبالتالي فإن احتمال الفشل في هذا اليوم يساوي 11(i+1)=ii+1. كما تعلمون ، للعثور على احتمالية حدوث عدة أحداث في وقت واحد ، من الضروري مضاعفة احتمال حدوث كل حدث. وبالتالي ، فإن الاحتمال الذي يمكن للمحلل أن يتأقلم معه في n أيام بالضبط هو(k=1n1kk+1)1n+1.

الأعضاء الذين يقفون تحت علامة العمل مسؤولون عن الفشل في كل من الأول(n1)أيام ، فأنت بحاجة إلى ضرب المنتج في احتمالية النجاح في اليوم التاسع عشر.
لذا ، لأي عدد من الأيام ، نحن نعرف احتمال النجاح لهذه الفترة بالضبط. نحن مهتمون باحتمال النجاح الكلي لكل فترة ممكنة تصل إلى 90 يومًا شاملة. الآن يمكنك استبدال الأرقام من 1 إلى 90 ، ولكن بالفعل في الصيغة الناتجة. أسهل طريقة هي كتابة حلقة في بعض الثعبان ستحسب وتضيف الاحتمالات ، وهو ما فعلته.

الرمز
import numpy as np

n = 90

probs = []

for i in range(1, n+1): #   

    prob_now = 1/(i+1) #      

    prob_not_before = []
    
    for k in range(1, i): #      
        prob_not_before.append(k/(k+1))
        
    prob_not_before = np.array(prob_not_before).prod() # 

    probs.append(prob_not_before * prob_now)

s = sum(probs) #   

print(s)


قرار الفقرة الثانية مشابه تمامًا للقرار الأول ، تختلف الصيغة فقط. سأترك الشفرة تحل النقطة الثانية - أعتقد أن كل شيء سيكون واضحًا.

النقطة 2
import numpy as np

n = 90

probs = []

for i in range(1, n+1): #   

    prob_now = 1/((i+1)**2) #      

    prob_not_before = []
    
    for k in range(1, i): #      
        prob_not_before.append(1 - (1/((k+1)**2)))
        
    prob_not_before = np.array(prob_not_before).prod() 

    probs.append(prob_not_before * prob_now)

s = sum(probs) #   

print(s)



المهمة 2. مصير الهامستر


المهمة


من أجل البقاء في فصل الشتاء ، قرر الهامستر الجائع الجشع سرقة مصنع الجوز الموجود على بعد 1000 متر من الحفرة. كان هناك 3000 صواميل متبقية في المصنع. يتم وضع 1000 صواميل كحد أقصى على خدود الهامستر. في كل مكان وبغض النظر عن نوع الهامستر ، كل متر يحتاج إلى تعزيزه بجوز واحد. الهامستر موجود بالفعل في المصنع وهو خطير. ما هو الحد الأقصى لعدد المكسرات التي يمكنه تخزينها؟ يجب تقريب الإجابة إلى أقرب عدد صحيح.

القرار


يذكرنا بقوة بمهمة جيب، اليس كذلك؟ لذا ، أمامنا تنوعها التالي. في الحالة العامة ، تظهر سيارة (في هذه الحالة ، الهامستر) في مشكلة السيارة الجيب ، والتي تحتاج إلى السفر لمسافة معينة في ظروف ذات سعة محدودة لحاوية الوقود (خدود الهامستر). الفكرة الكامنة وراء حل أي مشكلة في هذه الفئة - على طول الطريق ، يمكنك ترك إمدادات الوقود ، وكذلك العودة إلى مشكلة جديدة. خلاف ذلك ، لا توجد خوارزمية حل واحد ، حيث يمكن أن تكون الشروط والأهداف الأولية مختلفة تمامًا. الخيار المقترح هنا مثير للاهتمام لأنه من الضروري ليس فقط الانتقال من المصنع إلى الحفرة (وهو أمر أساسي ، لأن الهامستر يمكنه استيعاب 1000 صمولة بالضبط ، وهو ما يكفي لـ 1000 متر) ، ولكن نقل أكبر عدد ممكن من المكسرات إليه. من الأفضل رسم مخطط ،تصوير طول 1000 متر ومخزون من المكسرات في المصنع ، والتفكير في كيفية التصرف في الهامستر إذا كان يريد نقل 3000 صواميل إلى الحفرة ، مع تناول أقل قدر ممكن ، أي بعد مرور أقل قدر ممكن من المسافة الإجمالية. دعنا نحاول التحرك في أصغر خطوات ، 1 م لكل منهما ، ونقل جميع المكسرات 3000 معك في عدة رحلات.

من الواضح أنه من أجل نقل 3000 صواميل إلى أي نقطة ، سيحتاج الهامستر إلى العودة إلى الصنف السابق 3 مرات على الأقل. عندما تبقى 2000 صواميل ويؤكل الباقي على طول الطريق ، سيحتاج الهامستر إلى رحلتين إلى النقطة السابقة من أجل نقلها إلى أخرى جديدة. عندما يكون الوقود أقل من 1000 وحدة ، لن تضطر إلى العودة ، كل هذا يناسب خدود الهامستر. وبالتالي ، يمكن تقسيم عملية نقل المكسرات إلى ثلاث مراحل مقابلة. دعونا نرى ما سيكون الهامستر "استهلاك الوقود" في كل منهما. عندما يكون هناك أكثر من 2000 صواميل ، لتحريك متر واحد ، يجب على الهامستر:

  1. اجمع خدود المكسرات الكاملة وامش 1 متر
  2. قم بإفراغ 998 صواميل (1 أكلت على الطريق ، 1 اليسار للعودة)
  3. ارجع بمقدار 1 متر مرة أخرى إلى مخزون الجوز
  4. كرر الخطوات من 1-3 لألف جوز
  5. خذ آخر ألف واذهب معه 1 متر للأمام

وبالتالي ، فإن إزاحة متر واحد مع جميع الفرائس يكلف الهامستر 5 صواميل. عندما تصبح المكسرات <2000 ، وهذا يحدث بعد 200 متر من الحركة ، ستكون الخوارزمية على النحو التالي:

  1. اجمع خدود المكسرات الكاملة وامش 1 متر
  2. قم بإفراغ 998 صواميل (1 أكلت على الطريق ، 1 اليسار للعودة)
  3. ارجع بمقدار 1 متر مرة أخرى إلى مخزون الجوز
  4. خذ آخر ألف واذهب معه 1 متر للأمام

1 متر النزوح يكلف الهامستر 3 المكسرات. عندما يصل إلى نقطة 534 مترًا ، سيتم تناول ما مجموعه 2001 صواميل ، وسيتعين على الهامستر أن يأخذ آخر 999 صواميل ويذهب بهدوء إلى 466 مترًا المتبقية في الحفرة. عندما يصل إلى هناك ، ستبقى 533 صواميل في الخدين - سيكون هذا هو الحل للمشكلة.

أريد أن أشير إلى أن مهام هذا الفصل تحظى بشعبية كبيرة في نظرية الخوارزميات ، وكذلك في المقابلات في الشركات الكبيرة. أفضل طريقة لتعلم كيفية حلها هي الممارسة. لا توجد آلية واحدة لحلها (حسنًا ، أو سبحني) ، ولكن من الممكن جدًا مساعدتهم وتطوير التفكير الإبداعي.

المهمة 3. التوزيع التحليلي


المهمة


تريد Yandex إنشاء Mفرق المحللين. عند التوظيف ، يختار كل محلل بشكل عشوائي مجموعة لنفسه حيث سيعمل. يرغب قائد الفريق في تحديد الحد الأدنى لعدد الآلاف من المحللين الذي يكفي للتوظيف بحيث تكون مجموعته أكثر ترجيحًاP لم يكن أقل Nشخص؟

يجب عليك كتابة برنامج Python يقبلN، Mو Pفي سطر واحد ، ويعطي الناتج عدد الآلاف من المحللين.
1N100، 1M100000، 0P1

القرار


حسنًا ، المعرفة بالإحصاءات ، وبالتحديد التوزيع ذي الحدين ، كانت مفيدة . نشير إلى عدد المحللين الذين استأجرتهم ياندكسX. يختار كل محلل مستأجر فريقًا. من وجهة نظر قائد فريقنا ، فإن التعاقد مع محلل للعمل هو تجربة ذات نتيجتين: إما أن يدخل الوافد الجديد إلى فريقنا أم لا. احتمالية الضرب تساوي1M، احتمال أن يختار المحلل مجموعة أخرى ، على التوالي ، هو M1M. في المجموع ، ستكون مثل هذه التجارب مع اختيار الفريقX. عدد الضربات في فريقناn من Xيتم توزيع المحللين على حدين ، ووظيفة التوزيع تساوي:

P(nN)=k=0N(Xk)(1M)k(M1M)Xk

توضح هذه الوظيفة احتمالية أن يكون عدد النتائج أقل من أو يساوي العدد المحدد N. نحن مهتمون باحتمال أن يكون عدد النتائج أكبر من أو يساوي النتيجة المحددة ، لذا تبدو المهمة كما يلي:

X:1Px(nN)=P;X?


أي أنك بحاجة إلى العثور على عدد المحللين الذين تم تعيينهم Xحيث يحصل الفريق على الأقل Nشخص لاحتمال معين P.

حسنًا ، لقد توصلنا إلى حساب رياضي - كيف نجده الآنX؟؟؟ خرق. يمكنك كتابة دورة من شأنها فرز عدد المحللين المستأجرين ، وزيادتها حتى احتمالية الحصول على الأقلN لن يكون محللون مرضية.

الرمز
def c(n, k): #   ,    
    if 0 <= k <= n:
        nn = 1
        kk = 1
        for t in range(1, min(k, n - k) + 1):
            nn *= n
            kk *= t
            n -= 1
        return nn // kk
    else:
        return 0

def bin_prob(trials, k, m): #      

    return c(trials, k) * ((1/m)**k) * ((1 - 1/m)**(trials - k))

def cdf(maximum, trials, m): #   
    value = 0
    for i in range(maximum + 1):
        value += bin_prob(trials, i, m)
    return value

n, m, p = [(float(i)) for i in input().split()] #       
n = int(n)
m = int(m)


x = 1000 
while (1 - cdf(n, x, m)) < p: #      
    x += 1000 #   

print(int(x / 1000)) #  



المهمة 4. البحث عن الهدايا


المهمة


أحضر سانتا كلوز هدايا Anastasia 100 ووضعها تحت شجرة عيد الميلاد. الشجرة كبيرة ورقيقة ، لذا يصعب التنقل تحتها. يفحص Anastasia الهدايا بهذه الطريقة: يمتد بطريق الخطأ من الجانب العشوائي من الشجرة إلى نطاق عشوائي ، ويأخذ هدية ، ويفحصها ويعيدها. اتضح أنه في كل مرة يمكن أن تأخذ Anastasia على قدم المساواة أي هدية من أولئك الذين يرقدون تحت الشجرة. أوجد توقع حصة الهدايا التي ستأخذها أناستازيا بعين الاعتبار في 100 تمدد عشوائي؟

القرار


للوهلة الأولى ، تبدو المهمة بسيطة للغاية ، حتى تظهر الثقة أنه يمكن إيجاد حل من خلال صيغة أولية ، ولكن ليس كل شيء بهذه البساطة. ليس بسيط جدا. قضيت وقتًا غير لائق على هذه المهمة ، محاولًا رسم الخيارات واستخلاص صيغة ، لكنني لم أنجح. ثم ذهبت إلى Google ، ولدهشتي ، كان عليّ التعمق في المنتديات ، قبل أن أجد حلاً للحالة العامة . لذا ، إذا حددنا عشوائيًا عناصر من مجموعة ذات عائد ، فالاحتمال هوn مختارات من mعناصر المجموعة تنسحب تمامًا kيساوي مختلفة:

P(m,k,n)=(mk)k!S2(n,k)mn


أين S2هناك عدد ستيرلنغ من النوع الثاني - عدد الأقسام غير المرتبة للمجموعة منn العناصر قيد التشغيل kمجموعات فرعية غير فارغة. حسنًا ، من أجل العثور على التوقعات ، من الضروري جمع الاحتمالات المحسوبة بهذه الصيغة لكل جزء ممكن من الهدايا الفريدة التي تم فحصها - من مائة إلى واحد كامل. يمكن القيام بذلك باستخدام حلقة في Python.

الرمز
import math
import numpy as np
import sys
import sympy #     -   

sys.setrecursionlimit(10**9)

def c(n, k): # C

    return (math.factorial(n))/(math.factorial(k) * math.factorial(n-k))

def s(n, k): #      

    return sympy.functions.combinatorial.numbers.stirling(n, k)

    
def p(m, k, n): #    k

    return c(m, k) * math.factorial(k) * s(n, k) / (m**n)


pr = []
#      ,    ...
for j in range(1, 101): 
    pr.append(p(100, j, 100))
    
pr = np.array(pr)
#...    100
frac = np.array([i for i in range(1, 101)]) / 100


print(sum(pr*frac)) #  



المهمة 5. مسافر متوازن


المهمة


يبدأ المسافر في التحرك على طول حواف شبكة ثنائية الأبعاد مع عقد كاملة إلى اليمين أو أعلى بدقة. ينتقل من نقطة(0,0) بالضبط (100,100). ما مدى احتمال عبور نهر في خط مستقيم يربط بين نقطتي البداية والنهاية ، إذا افترضنا أن جميع المسارات المحتملة متساوية على حد سواء؟ ويعتقد أن المسافر عبر النهر إذا كان على نفس المسار بدقة فوق وتحت النهر. لا يعتبر دخول النهر تقاطعًا.

القرار


نجد احتمال عبور النهج الكلاسيكي - نقسم عدد الطرق ذات التقاطع على إجمالي عدد الطرق الممكنة. اسمحوا انn- طول حواف الشبكة المربعة. ثم العدد الإجمالي للمسارات الممكنة:

N=(2n!)(n!)2


يتم وصف اشتقاق الصيغة هنا . ولكن كيفية معرفة عدد طرق عبور النهر لكل منهاn؟؟؟ بعد أن حيرت من هذا السؤال ، قررت أخذ بعض أطوال الشبكة الأصغر ، ورسم الحقول وحساب عدد الطرق التي تعبر النهر يدويًا ، على أمل تتبع التبعية (أوصي بشدة أن تأخذ أيضًا ورقة وقلمًا وتجربة رسم شبكات ومسارات صغيرة).

يجب ألا يكون هناك شبكة بحجم 3 × 3 خلايا. يشغل النهر القطر الجانبي للشبكة ، ويكون المسافر في الركن الأيسر السفلي.

صورة
الرسم ليس مثاليًا ، لكنني حاولت بصدق



بمجرد أن قمت بالرسم ، أدركت أنه سيكون من الأسهل بكثير تتبع الطرق التي لا يعبرها النهر ، أي الطرق الموجودة أسفل النهر. بعد ذلك سيكون من الممكن مضاعفة عددهم في 2 ، وبالتالي مراعاة مسارات المرآة فوق النهر. نظرًا لأننا نعرف أيضًا العدد الإجمالي للطرق ، نجد عدد الأشخاص الذين يعبرون النهر. ولكن نعود إلى المهمة الرئيسية - نحن بحاجة إلى علاقة بينهماnوعدد مسارات عبور النهر.

في الشكل أعلاه لحالة 3x3 ، قمت بوضع علامة باللون الأزرق على بعض الطرق "البرية" التي يمكن للمسافر الوصول إليها: تمر المسارات المميزة على طول حواف الخلايا بإحداثية أفقية من 2 ، ولا يدخل المسافر الحواف اليسرى والعلوية للخلايا من قبل. هناك 3 طرق من هذا القبيل ، أيn. الآن دعونا نكتشف الطرق التي تمر عبر الخلية في العمود 1.

صورة


قمت بتمييز المسارات الجديدة باللون الأحمر. لذا ، من الواضح أنه إذا استدار مسافر إلى اليسار ثم الحافة العلوية للخلية (1 ، 0) ، فلن يتمكن من الوصول إليه سوى مسارين من المسارات الثلاثة عبر الخلايا بإحداثية أفقية من 2 ، لأنه يمكنك فقط التحرك لأعلى وإلى اليمين - المسار الثالث أقل . وبالتالي ، بإضافة خلية جديدة من العمود 1 إلى المسار ، قمنا بزيادة إجمالي عدد المسارات حسب عدد المسارات التي تمر عبر خلايا العمود 2 ، والتي ليست أقل من خليتنا الجديدة.

خذ شبكة 4 × 4 واستمر في حل التشابك. أصبح من الواضح أن إضافة خلية جديدة إلى عمود يزيد عدد المسارات حسب عدد المسارات التي تمر عبر العمود التالي بما لا يقل عن الحافة العلوية للخلية المضافة. لن أقوم بتمييز الطرق باللون ، سأقتصر على وصف النص ، ولكن إذا كنت تعتقد أنه من الضروري ، ارسم - في عملية الحل ، قمت برسم عشرات الشبكات المختلفة قبل أن أتمكن من الشعور بالاعتماد بثقة.

صورة


يعطينا العمود الموجود في أقصى اليمين مرة أخرى nالطرق. ستضيف إلينا الحافة العلوية للخلية (2 ، 0)n1طريق. ستتم إضافة الحافة العلوية للخلية (2 ، 1)n2طريق. ستضيف الحافة العلوية للخلية (1 ، 0) العديد من المسارات التي أضافتها الخلايا (2 ، 0) و (2 ، 1) معًا. إذا كنت ترغب في ذلك ، يمكنك رسم شبكة أكبر والاستمرار في التفكير في المسارات بنفس الخوارزمية. مهمتنا هي حساب المسارات لشبكة 100x100. للقيام بذلك ، يمكنك كتابة برنامج يقبل الإدخالn وبناء مصفوفة n×nبدءًا من العمود nثم لكل خلية من الأعمدة السابقة ، نحسب عدد المسارات التي أضافتها الخلية بناءً على بيانات العمود السابق. وبالتالي ، سيتم العثور على عدد المسارات غير النهرية.

الرمز
import numpy as np
import math

def routes_total(n): #   
    return math.factorial(2*n) / (math.factorial(n)**2)

def fill_matrix(n): #  ,       
    net = np.zeros((n, n)) 
    net[0, 0] = n #    n 
    for i in range(n-2):
        net[1, i] = n - i - 1 

    for i in range(2, n):
        for j in range(n - i - 1): 
            net[i, j] = 0
            for g in range(j, n - i + 1):
                net[i, j] += net[i - 1, g]
    
    #      2,     
    return (2 * sum(sum(net))) 

#      -    1
print(1  - fill_matrix(100) / routes_total(100))



المهمة 6. حالة التوزيع الخطي


المهمة


دولة التوزيع الخطي هي العديد من المدن ، بعضها متصل بطرق.

بمجرد أن أصبح ملك الدولة على علم بأن شعب نقاط التوقف كانوا على وشك غزو حدوده. بما أن الدولة لم تكن مستعدة للدفاع ، فقد اتخذ الملك قرارًا صعبًا - بتقسيم الدولة إلى العديد من الدول الصغيرة ، كل منها سيدافع بشكل مستقل عن حدودها.

وقد تقرر أنه يمكن بل ويجب ترك مدينتين في ولاية واحدة إذا كان من الممكن الوصول إلى المدينة الثانية من مدينة واحدة ، حتى إذا استولى شعب نقاط التوقف على طريق واحد بين أي مدينتين في ولاية التوزيع الخطي. في جميع الحالات الأخرى ، يجب أن تكون المدن في ولايات مختلفة.

على كل طريق سيقطع حدود أي دولتين جديدتين ، من الضروري وضع معقل. يعد ذلك ضروريًا في حالة قيام شعب نقاط التوقف بالتقاط إحدى هذه الحالات. ثم ستتمكن الثانية من الاستمرار في الدفاع عن حدودها. وبعبارة أخرى ، سيتم وضع الحصن على الطريق الذي يربط المدن من ولايات مختلفة.

طلب منك الملك أن تقدم له قائمة بالطرق التي يجب أن تضع عليها معاقل.

تنسيق البرنامج المدخلات والمخرجات
نمط الإدخال

nm— . (1n20000,1m200000). m . i bi,ei— , (1bi,ein)


b — , . b — , , . , .

, , , , — .


القرار


وهنا تكمن المشكلة في نظرية الرسم البياني. وراء القصص الطويلة حول مصير حالة التوزيع الخطي ، أخفى القائمون على الصياغة المهمة المثيرة للاهتمام إلى حد ما للعثور على الجسور في رسم بياني تكون عُقده مدنًا وحوافها طرقًا. باختصار ، الجسر هو حافة من الرسم البياني ، سيؤدي إزالته إلى قطع جزء معين من هذا الرسم البياني من القمم الأخرى. هذه هي فكرة الاستيلاء على الطريق - إذا تم التقاط الجسر ، فسيتم قطع الاتصال بين بعض المدن ، وإلا سيكون هناك دائمًا طريق بديل بين المدن ، وبالتالي فإن الجسور التي تقسمها الولايات ، من الضروري وضع معاقل على الجسور.

خوارزمية بحث الجسر استنادًا إلى بحث العمق(بحث العمق الأول ، DFS) - طريقة اجتياز الرسم البياني يتم فيها فحص جميع الحواف القادمة من قمة الرأس الأولية ، وإذا أدت الحافة إلى قمة لم يتم أخذها في الاعتبار بعد ، تبدأ الخوارزمية على الفور بشكل متكرر من هذا الرأس. سوف تساعد الحقيقة التالية في البحث عن الجسور:

لنفترض أننا ننظر بعمق ، وننظر الآن في جميع الحواف من قمة V. ثم إذا كانت الحافة الحالية (V ، U) بحيث أنه من قمة U ومن أي من أحفادها في شجرة الاجتياز ، لا يوجد معكوس المسار إلى ذروة V أو أي من أسلافه ، الحافة المعتبرة هي جسر.

من أجل معرفة كيفية التحقق من هذه الحقيقة في الرأس V ، نقدم وقت الدخول إلى قرص الرأس [V](من اللغة الإنجليزية. اكتشف). في هذا المتغير ، سيتم تسجيل خطوة الخوارزمية التي تم بها معالجة الرأس. أيضًا ، مع كل قمة V ، سيتم ربط المتغير الأدنى [V] ، الذي نكتب فيه وقت دخول الرأس الأول U ، والذي يمكن الوصول إليه من الرأس الخامس أثناء المعالجة الأولية للقمة الدنيا [V] = القرص [V] (إلى القمة قبل أنت لا تفهم نفسك) ، ولكن لاحقًا في عملية البحث بعمق ، يمكننا العثور على بعض الابن V ، الذي تؤدي إحدى حوافه إلى سلف V (دعنا نطلق عليه S). في هذه الحالة ، سنحدث أقل [V]: الأدنى [V] = القرص [S]. ومتى يمكننا ربط الجسر؟ ثم ، عندما نبحث بعمق ، نصل إلى القمة ، التي ليس لديها أبناء لم يتم أخذها في الاعتبار بعد (مرة أخرى ، نسميها U). في هذه الحالة ، سوف نتحقق من أقرب قمة يمكن الوصول إليها من U ، وإذا حدث هذا الرأس الأحدث بعد الوالد المباشر لـ U (هذا ممكن ، على سبيل المثال ، عندما لا يكون لدى U أبناء ، ثم أدنى [U] = قرص [U ] ) ، فإن اتصال U مع الوالد هو جسر.

تم إرفاق كود الخوارزمية المنفذة مع التعليقات أدناه. ومن المريح عدم إنشاء متغيرات منفصلة لل قرص و أدنى من كل قمة، ولكن لصفائف مكان لكل قيمة، حيث بلغ المؤشر عدد من قمة الرأس التي تنتمي إليها قيمة.

الرمز
import sys
from collections import Counter
import numpy as np
sys.setrecursionlimit(10**6) 

n, m = [int(i) for i in input().split()]
roads = [None] #    -    
graph = {}  #      ,    
for i in range(1, n+1):
    graph[i] = []
for i in range(1, m+1):
    twns = [int(j) for j in input().split()]
    graph[twns[0]].append(twns[1])
    graph[twns[1]].append(twns[0])
    roads.append(frozenset([j for j in twns]))
    
disc = [0] * (n+1) #  discovered
lowest = disc.copy() #  lowest
used = disc.copy() #  used. ,    
c = Counter(roads)

timer = 0 #   
nbridges = 0 #  
bridges = [] #  

def dfs(v, parent): #    ,    
    
    global timer
    global nbridges
    global bridges
    
    timer += 1 #   
    disc[v] = timer 
    lowest[v] = timer
    used[v] = True #     
    for u in graph[v]: #      
        if u == parent:
            continue #      ,    ,    
        if used[u]: #  ,    ,  
            lowest[v] = min(lowest[v], disc[u]) # ,       ;  lowest 
        else: #   
            dfs(u, v) #      
            #           cc  U:
            lowest[v] = min(lowest[v], lowest[u])  
            if lowest[u] > disc[v]: #   u    v   ,   
                twns = [] # ,  
                twns.append(u)
                twns.append(v)
                if c[frozenset(twns)] > 1: #     ,  ,    
                    continue
                nbridges += 1
                bridges.append(roads.index(set(twns)))

dfs(1, 0) #      

print(nbridges)
bridges = np.sort(bridges)
for bridge in bridges:
    print(bridge)



ساعدني المصدر التالي على التعامل مع المشكلة بطرق عديدة ، لذلك أرى أنه من الضروري ترك رابط لها. إنه يستحق المشاهدة وهنا هذا الفيديو - هناك رسوم متحركة جيدة للخوارزمية.

استنتاج


هذه هي المهام التي يجب على المتخصص الذي يتقدم للحصول على تدريب في Yandex حلها بثقة. أعطيت مجموعة المهام المذكورة أعلاه 5 ساعات - في وقت قصير ، في رأيي ، ولكن الجميع يعمل بالسرعة التي تناسبهم.

لقد تم اختبار قراراتي وهي تعطي الإجابات الصحيحة ، ولكن ليس لدي شك في أن هناك طرقًا أكثر فعالية للتعامل مع المهام المقترحة. إذا كنت على استعداد لتقديم حل أسرع أو أكثر قابلية للفهم أو إذا وجدت خطأ معي ، فلا تتردد في الكتابة عنه.

أتمنى للجميع أن يجدوا موقعاً لأنفسهم!

All Articles