A szöveg csak Firefox böngészőben jelenik meg helyesen. Használja a fenti PDF file-ra mutató link-et a letöltésre. Ezen a néven jelent meg a CEOI (Közép-Európai Informatikai Diákolimpia) idei első feladata. A teljes szöveg elérhető a következő címen:
1. feladat: Olvassuk el és értelmezzük a problémát, készítsünk néhány példát, rajzoljunk, majd próbáljuk meg egy-két mondatban összefoglalni, hogy pontosan mit kérdez a feladat.
A feladat téglalapok megszámolását kéri. Kombinatorikai feladatok között láthattunk már hasonlót: számoljuk meg, hogy az és pontok mint szemközti csúcsok által meghatározott, a koordinátatengelyekkel párhuzamos oldalú téglalapban hány olyan téglalap van, amelynek csúcsai egész koordinátákkal rendelkeznek és oldalai szintén a koordinátatengelyekkel párhuzamosak.
2. feladat: Adjunk módszert a téglalapok megszámolására egy oldalú téglalap esetén.
Válasszunk a téglalap kerületén vagy belsejében tetszőlegesen két egész koordinátájú pontot. Ha a választott pontok egyenese nem párhuzamos egyik tengellyel sem, akkor meghatároznak egy téglalapot úgy, hogy ezt a két pontot tekintjük két szemközti csúcsnak. Így az első csúcs elhelyezésére lehetőség adódik, míg a másodikra . Ezen a módon minden téglalapot négyszer számoltunk: kiválasztottuk a bal felső-jobb alsó csúcspárt kétszer, és a bal alsó-jobb felső csúcspárt is kétszer. Ezért a téglalapok száma A kacifántos kerítés téglalapokból épül föl, de a megszámolandó téglalapok átnyúlnak a kerítést alkotó téglalapokon, tehát a problémát még nem oldottuk meg. A továbbiakban vizsgáljuk a következő bemenethez tartozó kerítést. Az első sorban a kerítéselemek száma, a másodikban azok magassága és a harmadik sorban az elemek szélessége található ():
Helyezzük el a kerítés bal alsó sarkát a koordináta-rendszer középpontjába. Nézzük, hogyan lehetne a téglalapoknak csúcsokat választani a kerítésen. Látjuk, hogy például a és pontok, mint (bal, felső) és (jobb, alsó) csúcsok meghatároznak egy megszámolandó téglalapot, miközben az egyik csúcs a második (vagy harmadik), míg a másik csúcs a nyolcadik kerítéselemen található. 3. feladat: Találjunk ki egyszerű algoritmust, ami megszámolja a keresett téglalapokat.
Ha a kerítés méretei a példához hasonlóan kis számok, akkor nem is olyan nehéz algoritmust készíteni. Tekintsük a kerítés egész koordinátájú csúcsai közül azokat, amelyek lehetnek egy téglalap bal felső csúcsai, és válasszunk mindegyikhez megfelelő jobb alsó csúcsokat. Például a ponthoz jobb alsó csúcsként kiválaszthatjuk a , , pontokat. De az ponthoz már sokkal több jobb alsó csúcs válaszható, a legtávolabbi a . Az előbbi kombinatorikus gondolatmenethez hasonlóan megszámolhatjuk minden bal felső csúcshoz a lehetséges jobb alsó csúcsokat. Az pont esetén a jobb alsó csúcsok koordinátája 2-től 12-ig, míg koordinátája 0-tól 1-ig terjedhet. Így a meghatározott téglalapok száma . A feladat tehát megoldható úgy, hogy minden lehetséges bal felső csúcshoz meghatározzuk a tőle legtávolabbi olyan jobb alsó csúcsot, amivel olyan téglalapot alkot, melynek minden pontja a kerítésen van. A bal felső csúcshoz rajzolható összes téglalap benne van ebben az előbbi, legnagyobb téglalapban. Az fenti kombinatorikai gondolatmenethez hasonlóan ezek száma . A jobb alsó csúcs mindig a kerítés alján van, tehát , míg a jobb oldali utolsó megfelelő pont koordinátáját úgy kapjuk, hogy elindulunk az tengely pozitív irányában a pontból, és az első olyan kerítést alkotó téglalapnál megállunk, amelynek magassága kisebb, mint . Ha mind megfelelő, akkor a kerítés végéig megyünk. Nézzük a megoldás algoritmusát. A bemenet beolvasásakor értéket adunk az egész változónak, valamint a és egészeket tartalmazó méretű tömbnek. Ezeket, valamit a továbbiakban használt tömböket 0-tól indexeljük. Ezt a műveletsort végzi el a Beolvas() eljárás, amit nem részletezünk. Mivel a lehetséges bal felső csúcsoktól az tengely pozitív irányában elindulva keresni fogunk, ezért tudnunk kell az egyes kerítést alkotó téglalapok abszolút helyzetét az tengelyen. Ehhez hozzunk létre az tömböt, amelynek -adik eleme megadja a -adik kerítéselem bal oldalának koordinátáját , míg -edik eleme az utolsó kerítéselem jobb oldalának helyét, azaz a kerítés végét. Természetesen úgy is nézhetjük, hogy az a -adik kerítéselem jobb vége, vagyis jobb oldalának koordinátája. A tömb értékeinek számítását a következő eljárás végzi:
Eljárás TeglalapokEleje(N,w[0..N-1],el[0..N]) el[0] := 0 Ciklus k := 0-tól N-1-ig el[k+1] := el[k]+w[k] Ciklus vége Eljárás TeglalapokEleje vége
A továbbiakban megszámoljuk mindegyik kerítéselemnél azokat a téglalapokat, amelyek bal felső csúcsa a kerítéselemen, de nem annak jobb szélső oldalán található. Utóbbiakat a következő kerítéselemhez soroljuk. Mivel az utolsó kerítéselem jobb oldalán nem lehetnek bal felső csúcsok, így minden lehetséges bal felső csúcsot pontosan egyszer számolunk. A számítást a -adik kerítéselemre a KeritesElem() függvény végzi. Felülről lefelé haladunk, mivel a bal felső csúcsok -tól -ig helyezkedhetnek el. Minden felso szinthez ( értékhez) megnézzük, hogy meddig lehet jobbra elmenni a KeresJobb() függvény segítségével, majd a kerítéselem minden lehetséges bal értékétől kiszámoljuk a koordinátákból kiinduló téglalapok számát.
Függvény KeritesElem(N,h[0..N-1],w[0..N-1],k,el[0..N]) : Egész darab := 0 Ciklus felso := 1-től h[k]-ig meddig := KeresJobb(N,h,k,felso) jobb := el[meddig] Ciklus bal := el[k]-tól (el[k]+w[k]-1)-ig darab := darab + (jobb-bal)*(felso-0) Ciklus vége Ciklus vége KeritesElem := darab Függvény KeritesElem vége
A KeresJobb() függvény megadja, hogy a -adik kerítéselem felso magasságú pontjából meddig lehet jobbra menni úgy, hogy a kerítésen maradjunk. Minden -adik kerítéselem megfelelő, amely nem alacsonyabb a felso magasságnál. Ha mindegyik megfelelő, akkor a kerítés jobb végénél állunk meg. A függvény visszaadja az első nem megfelelő kerítéselem indexét, illetve -et, ha a kerítés végéig minden elem elég magas.
Függvény KeresJobb(N,h[0..N-1],k,felso) : Egész m := k+1 Ciklus amíg és m := m+1 Ciklus vége KeresJobb := m Függvény KeresJobb vége
A KacifantosKerites függvény adja a feladat megoldását: a beolvasás és az előkészítő műveletek után megszámolja az egyes kerítéselemekből bal felső csúcsokkal készíthető téglalapokat, és visszaadja azok -tel vett osztási maradékát.
Függvény KacifantosKerites() : Egész Beolvasas(N,h,w) TeglalapokEleje(N,w,el) darab := 0 Ciklus k := 0-tól N-1-ig darab := darab + KeritesElem(N,h,w,k,el) Ciklus vége KacinfatosKerates := darab MOD 1000000007 Függvény KacifantosKerites vége
Feltételezzük, hogy az algoritmusból készített programban az esetlegesen nagy számértékek elférnek a használt egész típusban és a műveletek nem okoznak túlcsordulást. A megoldás azonban a nagy számú és nagyságrendű bemenetekkel sajnos ettől függetlenül nem boldogul. A versenyen kevés pontot kapott volna, mivel a futásidő nagyobb és értékek esetén meghaladja az időkeretet. Ez érthető, hiszen a KeresJobb() függvény alkalommal hívódik meg, vagyis az kivételével minden kerítéselem magasságnál, míg a benne szereplő ciklus átlagos lépésszáma a kerítéselemek számával arányos. Tehát hatékonyabb algoritmusra lesz szükségünk, amiben nem szerepel a KeresJobb függvényhez hasonló keresés. Az előző algoritmus kerítéselemeken választott bal felső csúcsokat, és a jobb alsó csúcsokat tőle jobbra, a többi kerítéselemet vizsgálva kereste. Ne ragaszkodjunk feltétlenül a kerítés pontjainak ilyen csoportosításához.
4. feladat: Próbáljuk olyan téglalapokra bontani a kerítést, ahol magától adódik egy-egy bal felső csúcshoz a legtávolabbi jobb alsó csúcs.
Vegyük észre, hogy ha egy kerítéselem mindkét szomszédjánál magasabb, akkor azok a csúcsai, amelyek mindkét szomszéd magasságánál nagyobb koordinátával rendelkeznek, csak olyan csúcsokkal alkothatnak a kerítésen téglalapot, amelyek az adott kerítéselemen vannak. Például tekintsük a vizsgált bemenetnél a , , és csúcsok által meghatározott téglalapot. Az ezen fekvő bal felső csúcsokhoz ‐ kivéve az alsó oldalélt ‐ csak ezen a kerítéselemen lévő csúcsok közül választhatunk jobb alsót, tehát csak a értékek jöhetnek számításba. Ez éppen az adott kerítéselem két vége, tehát ekkor semmilyen tengely irányú keresésre nincs szükség. Gondoljuk tovább a dolgot. Ha az előbbi kerítésrészre eső bal felső csúcsokkal alkotott téglalapokat megszámoltuk, akkor gyakorlatilag elhagyhatjuk ezt a részét a kerítésnek, hiszen jobb alsó csúcsként is megszámoltuk minden pontját. Amennyiben további, hasonlóan kiemelkedő részek vannak a kerítésen, akkor azokban is számolhatunk. Így a példában a és a , , pontok által alkotott vagy az téglalap esetében. Az alábbi ábrán satírozással jelöljük a szóban forgó kiemelkedéseket.
Ezeket a kerítésrészeket elhagyva ismét olyan kerítés keletkezik, amelynek vagy vannak kiemelkedései, vagy egyetlen téglalap az egész kerítés. Így a számolás folytatható pl. a , , , téglalappal. Amennyiben lépésről-lépésre minden kiemelkedést a számolás után elhagyunk, akkor végül egy téglalap marad, amivel készen is vagyunk. A megoldáshoz tehát csak arra van szükség, hogy minél egyszerűbben meghatározzuk a kiemelkedéseket.
5. feladat: Gondolkozzunk olyan algoritmuson, ami az adatok alapján végigmegy a kiemelkedéseken és megszámolja azokat a téglalapokat, amelyek bal felső csúcsa ezeken található.
A cikk folytatásában választ adunk az 5. feladatra és hatékony algoritmust készítünk a versenyen kitűzött problémához. Természetesen a leírt gondolatmeneten kívül más ötlet alapján is lehet megoldást készíteni. Az utóbbi évek CEOI feladatai szerepelnek a http://mester.inf.elte.hu adatbázisában, így programunk helyességét és hatékonyságát ellenőrizhetjük a segítségével. |