กำลังนับความยาวของลำดับ Collatz - ตัววนซ้ำแบบกำหนดเองทำให้เกิดการชะลอตัวหรือไม่

ฉันได้แก้ไขปัญหา UVA #100 แล้ว - "The 3n + 1 ปัญหา". นี่คือปัญหา "ตัวอย่าง" ของพวกเขา โดยมีการจำกัดเวลาให้อภัยอย่างมาก (จำกัดที่ 3 วินาที โซลูชันตัวอย่างที่ไม่มีการแคชเลย รันใน 0.738 วินาที ซึ่งเป็นทางออกที่ดีที่สุดของฉันจนถึงตอนนี้ทำงานใน 0.016 วินาที) ดังนั้นฉันคิดว่าไม่ว่าจะทดลองโค้ดอย่างไร ฉันควรจะพอดีกับขีดจำกัดเสมอ ฉันคิดผิด

ข้อกำหนดเฉพาะของปัญหานั้นเรียบง่าย: แต่ละบรรทัดอินพุตมีตัวเลข 2 ตัว i และ j และเอาต์พุตควรพิมพ์ตัวเลขเหล่านี้ตามด้วยความยาวสูงสุดของลำดับ Collatz ซึ่งจุดเริ่มต้นอยู่ระหว่าง i ถึง j รวม

ฉันได้เตรียมวิธีแก้ปัญหาไว้สี่ประการ พวกมันค่อนข้างคล้ายกันและให้คำตอบที่ดีทั้งหมด

วิธีแก้ปัญหาแรกแคชความยาวลำดับสูงสุด 0x100000 ใน vector ความยาว 0 หมายถึงยังไม่ได้คำนวณความยาวของลำดับที่เริ่มต้นที่ตัวเลขนี้ มันทำงานเร็วพอ - 0.03 วินาที

วิธีแก้ไขปัญหาที่สอง ค่อนข้างคล้ายกัน เพียงแต่แคชทุกความยาวในอาร์เรย์แบบกระจัดกระจายที่ใช้กับ unordered_map มันทำงานค่อนข้างช้ากว่าโซลูชันก่อนหน้า แต่ก็ยังพอดีในขีดจำกัด: 0.28 วินาที

เพื่อเป็นแบบฝึกหัด ฉันยังได้เขียนวิธีแก้ปัญหาที่สาม ซึ่งอิงจากวิธีแก้ปัญหาที่สอง เป้าหมายคือการใช้ฟังก์ชัน max_element ซึ่งยอมรับเฉพาะตัววนซ้ำเท่านั้น ฉันไม่สามารถใช้ unordered_map::iterator สำหรับการเพิ่มตัววนซ้ำเช่น AFAIK ขนาดแผนที่เชิงเส้น ดังนั้นฉันจึงเขียนตัววนซ้ำที่กำหนดเองซึ่งทำงานบน "คอนเทนเนอร์" แบบนามธรรมที่ "เก็บ" ความยาวลำดับของทุกจำนวนที่เป็นไปได้ (แต่อันที่จริงคำนวณและแคชไว้ตามความจำเป็นเท่านั้น) ในแกนกลางของมันจริง ๆ แล้วมันเป็นโซลูชัน unordered_map เดียวกัน - มีเพียงตัววนซ้ำเลเยอร์เพิ่มเติมที่เพิ่มไว้ด้านบน วิธีแก้ปัญหาไม่พอดีใน 3 วินาที ขีด จำกัด

และมาถึงเรื่องที่ฉันไม่เข้าใจ ในขณะที่เห็นได้ชัดว่าโซลูชันที่สามนั้นซับซ้อนเกินไปโดยตั้งใจ แต่ฉันแทบจะไม่เชื่อเลยว่าตัววนซ้ำอีกชั้นหนึ่งจะทำให้เกิดการชะลอตัวเช่นนี้ได้ เพื่อตรวจสอบสิ่งนี้ ฉันได้เพิ่มเลเยอร์ตัววนซ้ำเดียวกันให้กับโซลูชัน vector นี่คือโซลูชันที่สี่ของฉัน เมื่อพิจารณาจากสิ่งที่แนวคิดตัววนซ้ำนี้ทำกับโซลูชัน unordered_map ของฉัน ฉันคาดว่าจะมีการชะลอตัวอย่างมากเช่นกัน แต่น่าแปลกที่สิ่งนี้ไม่ได้เกิดขึ้นเลย โซลูชันนี้ทำงานเกือบเร็วเท่ากับ vector ธรรมดาใน 0.035 วินาที

สิ่งนี้เป็นไปได้อย่างไร? อะไรเป็นสาเหตุของการชะลอตัวในแนวทางที่สาม? เป็นไปได้อย่างไรที่การแก้ปัญหาสองวิธีที่คล้ายกันมากเกินไปในลักษณะเดียวกันจะทำให้โซลูชันหนึ่งทำงานช้าลงอย่างมาก และแทบจะไม่สร้างความเสียหายให้กับอีกโซลูชันเลย เหตุใดการเพิ่มเลเยอร์ตัววนซ้ำลงในโซลูชัน unordered_map จึงทำให้ไม่เหมาะสมกับเวลา และการทำแบบเดียวกันกับโซลูชัน vector แทบจะไม่ทำให้โซลูชันช้าลงเลย

แก้ไข:

ฉันพบว่าปัญหาดูเหมือนจะชัดเจนที่สุดหากอินพุตมีบรรทัดที่ซ้ำกันหลายบรรทัด ฉันทดสอบวิธีแก้ปัญหาทั้งสี่บนเครื่องของฉันกับอินพุต 1 1000000 ซ้ำ 200 ครั้ง วิธีแก้ปัญหาด้วยเวกเตอร์ธรรมดาจะประมวลผลทั้งหมดภายใน 1.531 วินาที การแก้ปัญหาด้วยเวกเตอร์และเลเยอร์ตัววนซ้ำเพิ่มเติมใช้เวลา 3.087 วินาที การแก้ปัญหาด้วยแผนที่แบบไม่เรียงลำดับธรรมดาใช้เวลา 33.821 วินาที และวิธีแก้ปัญหาด้วยแผนที่แบบไม่เรียงลำดับและเลเยอร์ตัววนซ้ำเพิ่มเติมที่จัดการใช้เวลามากกว่า ครึ่งชั่วโมง - ฉันหยุดมันหลังจาก 31 นาที 0.482 วินาที! ฉันทดสอบบน Linux mint 17.2 64 บิต, เวอร์ชัน g++ Ubuntu 4.8.4-2ubuntu1~14.04 พร้อมแฟล็ก -std=c++11 -O2, โปรเซสเซอร์ Celeron 2955U @1.4 GHz x 2


person Community    schedule 22.07.2015    source แหล่งที่มา
comment
ฉันไม่สามารถทำให้เกิดปัญหาซ้ำได้ โซลูชันที่สามดำเนินการเสร็จภายในเวลาเพียงเล็กน้อยบนเครื่องของฉันสำหรับโปรแกรมและอินพุตที่กำหนด (time รายงาน 1-2 มิลลิวินาที) เช่นเดียวกับวินาที บางทีอาจโพสต์ไฟล์อินพุตที่ยากขึ้น?   -  person Potatoswatter    schedule 22.07.2015
comment
… การเปลี่ยนบรรทัดสุดท้ายเป็น 900 1000000 จะเพิ่มเวลาเป็นช่วง 1 วินาที: โซลูชันที่สามเสร็จสิ้นใน 1 วินาที และโซลูชันที่สองเสร็จสมบูรณ์ใน 2 วินาที ดูเหมือนจะไม่ไวต่อระดับการปรับให้เหมาะสมหรือ GCC กับ Clang มากเกินไป   -  person Potatoswatter    schedule 22.07.2015
comment
@Potatoswatter UVA ทำงานเช่นนี้: คุณนำเสนอไฟล์ต้นฉบับให้พวกเขา คอมไพล์มันและรันบนเครื่องของพวกเขา ป้อนโปรแกรมของคุณด้วยอินพุต ซึ่งโดยปกติจะค่อนข้างยาก หากโปรแกรมของคุณไม่เสร็จสิ้นภายในเวลาที่กำหนด (3 วินาทีที่นี่) พวกเขาจะตัดสินว่าไม่ถูกต้อง น่าเสียดายที่พวกเขาเก็บข้อมูลที่ป้อนไว้เป็นความลับ ดังนั้นฉันจึงไม่สามารถโพสต์ไว้ที่นี่ได้ พวกเขาเผยแพร่อินพุตตัวอย่างบางส่วนซึ่งเป็นสิ่งที่ฉันโพสต์ไว้ที่นี่ แต่โดยปกติแล้วมันจะมีน้ำหนักเบามากจนแทบจะไม่เหมาะสำหรับการทดสอบใดๆ เวลาที่ฉันได้โพสต์คือเวลาที่โซลูชันทำงานบนเซิร์ฟเวอร์และอินพุต   -  person    schedule 22.07.2015
comment
@Potatoswatter ฉันรันโปรแกรมบนเครื่องของฉันกับอินพุตของ 1 1000000 ผลลัพธ์: วิธีแก้ปัญหาด้วย vector ธรรมดา: 0.046 วินาที; วิธีแก้ปัญหาด้วย vector และตัววนซ้ำ: 0.064 วินาที; วิธีแก้ปัญหาด้วย unordered_map: 1.26 วินาที; และวิธีแก้ปัญหาด้วย unordered_map และเลเยอร์ตัววนซ้ำเพิ่มเติม: 2.656 วินาที BTW คุณแน่ใจในการป้อนข้อมูลของคุณว่าโซลูชันที่สามเสร็จสมบูรณ์เร็วกว่าโซลูชันที่สองหรือไม่ นั่นจะน่าสนใจ... การเพิ่มตัววนซ้ำเหล่านั้นจะเร่งเวลาดำเนินการได้อย่างไร   -  person    schedule 22.07.2015
comment
ใช่ ฉันแน่ใจว่าโซลูชัน #2 (unordered_map เท่านั้น) เร็วกว่า #3 (บวก lengthsbase::iterator) สองเท่าบนเครื่องของฉัน ฉันไม่รู้ว่าทำไม ฉันใช้ GCC 5.1 และ Clang 3.6 บน OS X บน Haswell 2.3 GHz Core i7 Macbook อย่างไรก็ตาม แม้ว่าตัวเลขของคุณจะเป็น 2:1 ในอีกทางหนึ่ง แต่ก็ยังให้ผลลัพธ์ที่แตกต่างไปจากเดิมอย่างสิ้นเชิงโดยไม่แปลกใจกับสิ่งที่กรรมการออนไลน์พูด   -  person Potatoswatter    schedule 22.07.2015
comment
ประเด็นของฉันไม่ได้เกี่ยวกับข้อมูลที่ผู้ตัดสินออนไลน์ใช้ข้อมูลมากนัก เนื่องจากสิ่งที่คุณเชื่อว่าพวกเราใน SO ควรใช้ ไม่ควรให้ผู้ตอบแต่ละคนออกแบบแบบทดสอบของตนเองจะดีกว่า   -  person Potatoswatter    schedule 22.07.2015
comment
เอ๊ะ ความคิดเห็นอื่นนั้นถอยหลัง ไม่เร็วเป็นสองเท่า แต่ยาวเป็นสองเท่า ฉันตรวจสอบอีกครั้ง จริงๆ.   -  person Potatoswatter    schedule 22.07.2015
comment
@Potatoswatter ฉันทดสอบวิธีแก้ปัญหาทั้งสี่กับอินพุต 1 1000000 ซ้ำ 200 ครั้ง เวกเตอร์ธรรมดา: 1.531 วินาที; เวกเตอร์พร้อมตัววนซ้ำ: 3.087 วินาที; แผนที่ไม่เรียงลำดับธรรมดา: 33.821 วินาที; แผนที่ที่ไม่เรียงลำดับพร้อมตัววนซ้ำ: ฉันไม่รู้ หยุดมันหลังจาก 31 นาที 0.482 วินาที Linux mint 17.2 64 บิต, เวอร์ชัน g++ Ubuntu 4.8.4-2ubuntu1~14.04 พร้อมแฟล็ก -std=c++11 -O2, โปรเซสเซอร์ Celeron 2955U @1.4 GHz x 2   -  person    schedule 22.07.2015


คำตอบ (1)


ดูเหมือนจะมีปัญหาใน GCC 4.8 มันไม่เกิดขึ้นใน 4.9 ขึ้นไป ด้วยเหตุผลบางประการ ลูปภายนอกที่ตามมา (ที่มีแคช unordered_map ที่เติมข้อมูล) จะทำงานช้าลงเรื่อยๆ ไม่ใช่เร็วขึ้น ฉันไม่แน่ใจว่าทำไม เนื่องจาก unordered_map ไม่ได้ใหญ่ขึ้น

หากคุณไปที่ลิงก์นั้นและเปลี่ยน GCC 4.8 เป็น 4.9 คุณจะเห็นลักษณะการทำงานที่คาดไว้ซึ่งการทำงานที่ตามมาในช่วงตัวเลขเดียวกันจะเพิ่มเวลาเล็กน้อยเนื่องจากพวกมันใช้ประโยชน์จากแคช

โดยรวมแล้ว ปรัชญาของการเป็น "อนุรักษ์นิยม" ด้วยการอัปเดตคอมไพเลอร์นั้นล้าสมัยไปนานแล้ว คอมไพเลอร์ทุกวันนี้ได้รับการทดสอบอย่างเข้มงวด และคุณควรใช้รีลีสล่าสุด (หรืออย่างน้อยก็บางอันล่าสุด) เพื่อการพัฒนาในชีวิตประจำวัน

การที่กรรมการออนไลน์ตัดสินให้คุณเจอข้อบกพร่องที่แก้ไขมานานนั้นช่างโหดร้าย

person Potatoswatter    schedule 23.07.2015
comment
ขอบคุณมาก. ผู้ตัดสินบอกตัวเองว่าใช้ *4.8.2 - GNU C++ Compiler พร้อมตัวเลือก: -lm -lcrypt -O2 -std=c++11 -pipe -DONLINE_JUDGE สำหรับ C++11 สำหรับฉัน ฉันแค่ใช้เวอร์ชันมาตรฐานจากที่เก็บมาตรฐาน... ฉันจะต้องเปิดใช้งานแบ็คพอร์ตหากฉันจะอัพเกรดคอมไพเลอร์ ฉันเกรงว่า... - person ; 23.07.2015
comment
โปรดดูเกณฑ์มาตรฐานที่ฉันสร้างไว้ด้วย: melpon.org/wandbox/permlink/XRocZilmeRupsDk9 ที่นี่เรามี สถานการณ์ตรงกันข้าม: gcc 4.8.2 สร้างโปรแกรมที่ทำงานใน ~7 วินาที ในขณะที่ gcc 4.9.0 และ even gcc 5.2.0 และ even gcc 6.0.0 สร้างโปรแกรมที่ถูกฆ่าโดย Wandbox เนื่องจากทำงานนานเกินไป เกณฑ์มาตรฐานค่อนข้างคล้ายกับปัญหาที่กล่าวถึงที่นี่ - person ; 23.07.2015
comment
ดูเหมือนว่า BTW Clang 3.6.0 จะทำงานได้ดีในลิงก์ Wandbox ทั้งสอง - person ; 23.07.2015