Musala Soft Logo Конкурс по програмиране на Musala Soft и PC Magazine Bulgaria PC Magazine Bulgaria Logo
  Състезателна система
Сезон 2010 - 2011
Сезон 2009 - 2010
Сезон 2008 - 2009
Сезон 2007 - 2008
Сезон 2006 - 2007
Сезон 2005 - 2006
Сезон 2004 - 2005
Сезон 2003 - 2004
Сезон 2002 - 2003
Сезон 2001 - 2002
Сезон 2000 - 2001
Правила
Задача 1
Задача 2
Задача 3
Задача 4
Задача 5
Задача 6
Класиране
Финален кръг
Анализ на Задача 4 от конкурса на Musala Soft & PC Magazine - Оцветяване

Поставената задача не е безизвестна и вероятно част от участниците в конкурса са я срещали в една или друга форма. Въпреки това тази задача беше избрана от журито, заради множеството подходи за решаване, които тя позволява, заради изобретателността и нестандартния анализ, които доброто й решаване изискват.

Очевидно и леко за реализация решение е последователно да се изчерпват възможностите за оцветяване. Backtracking техника би намерила решение, но времето което експоненциалната й сложност (O(2^N)) изисква прави този вариант неприемлив - той би намерил решение за приемливо време само когато броят на точките е много малък. Добри можем да наречем решения със сложност O(N^2), O(N*logN), O(N). Решения с такава сложност предполагат добър анализ на възможните конфигурации и техните елементи и някаква "умна" и бърза стратегия за оцветяване.

Ще изложим решението на журито. Стратегията, която то реализира е показателна със своята простота и висока ефективност - сложност O(N). Нека първо анализираме поставената цел - в целевата конфигурация не е от значение как точно са подредени точките, а е важно по всеки хоризонтал и всеки вертикал броят на черните и броят на белите точки да се различават най-много с единица. Това, от своя страна означава, че по всяка линия (хоризонтална или вертикална), на всяка една черна/бяла точка може да се съпостави една бяла/черна точка, като евентуално една точка от линията може да остане "самотна" - да няма съответна с противоположен цвят. Ако имаме целевото оцветяване, тривиално e да намерим такова разбиване на точките по двойки по хоризонтали и вертикали, като най много по една точка да остане "самотна" на хоризонтал и вертикал.

Сега ще се убедим и в обратното - ако имаме разбиване на точките по двойки по указания начин, то лесно ще намерим и решение. Нека сме разбили точките по двойки (като на нито една точка все още не е присвоен цвят) и следваме следната стратегия за оцветяване (алгоритъмът е описан в псевдо код):
Докато съществува неоцветена точка (точка A), повтаряй:
начало
    1. оцветяваме А в произволен цвят (например бяло)
    2. обявяваме A за текуща точка
    3. обявяваме цвета на точка A за текущ цвят
    4. Ако текущата точка има съответна (учавства в двойка с) неоцветена точка по хоризонтала (точка B), то:
        4.1. оцветяваме точка B в цвят противоположен на текущия цвят.
        4.2. рекурсивно изпълняваме стъпка 5 като за текущи точка и цвят обявяваме точка B и нейния цвят.
    5. Ако текущата точка има съответна (учавства в двойка с) неоцветена точка по вертикала (точка B), то:
        5.1. оцветяваме точка B в цвят противоположен на текущия цвят.
        5.2. рекурсивно изпълняваме стъпка 4 като за текущи точка и цвят обявяваме точка B и нейния цвят.
край.

Ще покажем, че следното условие е инвариантно, т.е остава изпълнено след всяка итерация на цикъла: на всяка линия броят на черните точки е равен на броя на белите точки, ако самотната точка на тази линия (ако такава съществува) не е оцетена; в противен случай броят на черните точки се различва с единица от броя на белите. Очевидно това условие е изпълнено в началото, когато нито една точка не е оцветена. Ще покажем, че то остава в сила след всяка итерация на описания алгоритъм. От това следва, че след приключването на алгоритъма задачата ще е решена, защото условието ще е изпълнено и всички точки ще са оцветени. Нека разгледаме какво представлява една итерация на алгоритъма: на практика всяка итерация оцветява максималната верига от точки, такава че съдържа първоначално избраната точка и:
      1. веригата е алтернираща по хоризонтали и вертикали, т.е. всеки три последователни точки точки от веригата образуват прав ъгъл (а не лежат на една права)
      2. всички точки от веригата са неоцветени
Веригата е максимална когато не можем да я разширим повече нито от единия, нито от другия й край. Т.е. за всеки от краищата й имаме че: или точката там не участва в двойка (т.е. това е самотната точка на съответната линия), или точката, с която образуват двойка е вече оцветена. Сега е моментът да обърнем внимание по какъв начин сме оцветили веригата - всеки две съседни точки са оцветени в различен цвят. Лесно се вижда, че междинните точки от веригата (тези, които имат съседи по веригата и от двете си страни) не променят разликата между броя на черните и броя на белите точки на линиите, на които лежат - това е така, защото за всяка от тях на хоризонтала и верикала, на който лежи са добавени по една черна и една бяла точка. Повече внимание изискват граничните случаи - точките, които са краища на веригата. Ако една точка е крайна защото е самотна, то преди нейното оцветяване броят на черните точки е бил равен на броя на белите точки по съответната линия (това следва от инвариантното условие) и след нейното оцветяване разликата между черните и белите точки ще е единица, т.е. отново запазваме условието. Ако една точка е крайна, защото съответната й е вече оцветена (това може да се случи само ако съответната й точка е от същата верига), то те задължително ще са с различни цветове и условието отново ще се запази. Двете точки са с различни цветове заради условието за алтерниране на веригата и съответното алтерниране на цветовете, т.е. за да стигнем до линия, която вече сме посетили трябва да сме сменили цвета нечетен брой пъти и следователно сме "донесли" противоположния на вече оцветената точка цвят. С това доказахме инвариантността на условието, а съответно и коректността на алгоритъма. Забележете, че посоченият алгоритъм не прави "грешни" ходове - всяка точка се оцветява веднъж и повече не се разглежда - това определя линейната сложност на алгоритъма и високата му скорост.

Остана единствено да покажем как да разделим точките по двойки при дадена начална конфигурация - това разбира се е тривиална задача, но даже и тук може да се прояви съобразителност и да се спести време и памет. Това можем да сторим паралелно с прочитането на входните данни. Тъй като възможните стойности на координатите на точките са силно ограничени, можем да помним последната точка от всяка линия, която все още не е свързана с друга по линията - когато нова точка от тази линия постъпи от входа - маркираме двете точки като двойка в съответните структури и освобождаваме линията да "чака" за следващата двойка.


Анализи на решения изпратени от участници:

  • Стоян Йорданов (RTF Format)

  • За въпроси можете да ни пишете на адрес: konkurs@musala.com.

    Supported by Musala Soft Ltd.

    Copyright 2000-2010 by Musala Soft Ltd. All rights reserved.