Prog 2 2. verseny feladatsor

Utolsó módosítás: 2008. november 12.

Ez a feladatsor az első verseny harmadik, november 14. péntek délelőtti turnusához tartozik.

Le lehet tölteni az összes példa be- és kimenetet és példa forráskódot egyben: z2p.tgz vagy z2p.zip.

0. feladat (z2f0): lefele lépések száma

A program bemenete egy sorból áll, amiben tizennégy szám áll szóközzel elválasztva. Mindegyik szám 0 és 1000 közti egész. A kimenet egyetlen sorába egy számot kell beírni, mégpedig azt, hányszor fordul elő a bemenetben, hogy valamelyik szám kisebb a közvetlenül előtte álló számnál. A válasz tehát nyilvánvalóan legalább 0, de legfeljebb 13.

(A bemenetet a standard bemenetről olvassa, a kimenetet a standard kimenetre írja. A kimenetben a számot a legrövidebb decimális alakban kell kiírni, a sort újsor jellel kell lezárni.)

Például ha a bemenet a következő:

990 964 38 247 506 955 711 775 817 27 543 403 886 461

akkor a kimenet:

6

mivel a bemenetben a 964 kisevv a közvetlenül előtte álló 990-nél, és később a 38, 711, 27, 403, 461 számok is ilyenek.

z2f0be1 -> z2f0ki1

z2f0be2 -> z2f0ki2

z2f0be3 -> z2f0ki3

z2f0be4 -> z2f0ki4

z2f0be0 -> z2f0ki0

1. feladat (z2f1): körlevél hosszú vonallal

Ez a feladat az 1. mintafeladatsor 6. feladatának (körlevél írása) módosítása. A feladat eléggé hasonlít, így csak a különbségeket sorolom fel. A megoldáshoz érdemes a mintafeladat s1f6.c mintamegoldásból kiindulni. (Érdemes nézni az ott szereplő magyarázatot is, mert úgy könnyebb a forrást megérteni.)

A feladat abban különbözik az eredetitől, hogy a körlevél sablonban van egy különleges jelölő, ami helyett nem egy mezőt, hanem egy vízszintes vonalat szúrunk be.

Pontosan a következők az új szabályok. A sablonban két féle jelölő van. A mező jelölő továbbra is a @i, ez a rekord következő mezőjének értékét másolja be a levélbe. A sablonban ezen kívül van valahol pontosan egy @h jelölő. Legfeljebb ötven közönséges mezőjelölő lehet, de ebben a feladatban kivételesen megengedjük azt is, ha egy sincs.

A rekordok megnyitó jelzése egy sor, amiben egy pozitív szám áll, de ez a szám az eredeti feladattal ellentétben nem feltétlenül egy, hanem 1-től 100-ig bármi lehet. Ez a szám adja meg, milyen hosszú lesz a vízszintes vonal a megfelelő levélben. A rekordban amúgy minden közönséges mező jelzéshez mező érték tartozik, aminek az értékét a jelölő helyére be kell szúrni. A különleges @h jelölőhöz nem tartozik mező a rekordban.

A levelet úgy kapjuk, hogy a sablonban a közönséges jelölőket a mezők értékére kicseréljük. Ezen kívül a különleges @h jelölő helyére be kell írni pontosan annyi darab mínusz (-) karaktert, ahányas szám áll a rekord fejlécében.

Lássunk egy példát, ez talán világosabbá teszi a dolgot. A bemenet a következő.

Kedves @i!
Csodálatosan érzzük magunkat a @i nyaraláson.
Tegnap délelőtt megnéztük a @i, hát az gyönyörű volt.
Este a városban sétáltunk, ilyenkor nagyon hangulatos.
Ma lementünk a tengerhez, és pecáztunk.  El se fogod hinni, de
<@h>
ekkora halat fogtam.  
Csak azt sajnálom, hogy Te nem lehetsz velünk,
és hogy csak ilyen kitöltögetős levelezőlapot kaptam.
Sokat gondolok rád,
@i

$
8
Györgyi
Párizsi
Sacre-Coeur-t
Andi
30
Laci
Velencei
Dózse-palotát
Picur
12
Sára
Londoni
Big Bent
Ernő
0

Ebből a következő kimenetet kapjuk.

Kedves Györgyi!
Csodálatosan érzzük magunkat a Párizsi nyaraláson.
Tegnap délelőtt megnéztük a Sacre-Coeur-t, hát az gyönyörű volt.
Este a városban sétáltunk, ilyenkor nagyon hangulatos.
Ma lementünk a tengerhez, és pecáztunk.  El se fogod hinni, de
<-------->
ekkora halat fogtam.  
Csak azt sajnálom, hogy Te nem lehetsz velünk,
és hogy csak ilyen kitöltögetős levelezőlapot kaptam.
Sokat gondolok rád,
Andi

Kedves Laci!
Csodálatosan érzzük magunkat a Velencei nyaraláson.
Tegnap délelőtt megnéztük a Dózse-palotát, hát az gyönyörű volt.
Este a városban sétáltunk, ilyenkor nagyon hangulatos.
Ma lementünk a tengerhez, és pecáztunk.  El se fogod hinni, de
<------------------------------>
ekkora halat fogtam.  
Csak azt sajnálom, hogy Te nem lehetsz velünk,
és hogy csak ilyen kitöltögetős levelezőlapot kaptam.
Sokat gondolok rád,
Picur

Kedves Sára!
Csodálatosan érzzük magunkat a Londoni nyaraláson.
Tegnap délelőtt megnéztük a Big Bent, hát az gyönyörű volt.
Este a városban sétáltunk, ilyenkor nagyon hangulatos.
Ma lementünk a tengerhez, és pecáztunk.  El se fogod hinni, de
<------------>
ekkora halat fogtam.  
Csak azt sajnálom, hogy Te nem lehetsz velünk,
és hogy csak ilyen kitöltögetős levelezőlapot kaptam.
Sokat gondolok rád,
Ernő

Itt van még néhány másik bemenet is.

z2f1be0 -> z2f1ki0

z2f1be1 -> z2f1ki1

z2f1be2 -> z2f1ki2

z2f1be3 -> z2f1ki3

z2f1be4 -> z2f1ki4

z2f1be5 -> z2f1ki5

A módosított körlevél programot a SIO-ba a z2f1 feladatra kell feltölteni.

Végül egy tanács: a rekord kezdősorából a számot a sscanf(line, "%d", &i) hívással olvassuk be az int típusú i változóba.

2. feladat (z2f2): buszos átszállás

Először nézzük a feladat egyszerűbb, átszállás nélküli változatát.

Valaki busszal utazik egy helyről egy másik helyre. Azt már kiválasztotta, hogy hol száll fel a buszra, és hol száll le, de most a menetrendet böngészi, hogy melyik buszra kell felszállnia ezen a vonalon. A bemenet meg fogja adni a buszok menetrendjéből az felszállás és a leszállás időpontját a megfelelő megállókon. Ezen kívül a bemenet megadja azt is, mennyit kell még sétálnia az illetőnek, amíg a buszmegállóhoz kiér az indulási helyéről, mennyit kell sétálnia a leszállástól a megérkezésig, és hogy legkésőbb mikorra szeretne odaérni. Egyszerűsítésként feltesszük, hogy a buszok teljesen pontosan járnak. Ezekből az adatokból a programnak ki kell számolnia, mikor kell elindulni legkésőbb.

A bemenetben és a kimenetben minden időpont és időtartam egész perc lesz, az időpontokat óra:perc formátumban adjuk meg (az órát és a percet is pontosan két számjeggyel), és minden időpont ugyanaznap éjfél és a következő éjfél közé esik.

A bemenet első sorában egy egész szám, egy időpont, majd egy egész szám áll (köztük egy-egy szóköz). Az első szám a séta hosszát adja meg az indulástól a buszmegállóig percben mérve, az időpont azt adja meg, legkésőbb mikorra kell odaérnie az illetőnek, ahova utazik, majd a második szám a séta hosszát a megérkezés előtt a leszállástól szintén percben mérve.

A második sorban egyetlen szám áll, a megfelelő megállók között közlekedő buszjáratok száma. Ez a szám legalább 1, de legfeljebb 50.

Ezután az összes többi sor egy buszjárathoz tartozik. Minden ilyen sorban két időpont van (köztük szóköz): az első a busz indulási időpontja abból a megállóból, ahol emberünk fel szeretne szállni, a második az érkezés időpontja abba a megállóba, ameddig utazni szeretne. A buszok különböző ideig mehetnek a két megálló között, akár meg is előzhetik egymást – nem feltétlenül ugyanazon az úton járnak. A buszokat tetszőleges sorrendben sorolja fel a bemenet.

A kimenetbe egyetlen időpontot kell kiírni: azt a legkésőbbi időpontot, amikor el kell indulnia emberünknek, hogy még pont elérjen egy olyan buszt, amivel időben megérkezik. Az időpont után újsor jel áll. Azt felteheti, hogy legalább egy busz megfelelő.

Nézzünk egy példát. Legyen a bemenet a következő.

5 08:15 10
8
07:50 08:30
07:31 08:04
07:27 08:03
07:35 08:05
07:45 08:07
07:40 08:06
07:15 08:00
07:51 08:15

Mivel 8 óra 15 percre meg kell érkezni, és ez előtt még tíz percet sétálni is kell, csak olyan buszok jönnek szóba, amelyek legkésőbb 8 óra 5 perckor megérkeznek. Van is egy busz, ami pont ekkor érkezik, ez 07:35-kor indul. Van még három másik busz is, amelyik 08:05 előtt megérkezik, de mindegyik 07:35-nél előbb is indul. Így aztán 07:35-re kell odaérni a buszmegállóba, de előbb még öt percet sétálni kell, tehát otthonról legalább 07:30-kor el kell indulni. A kimenetbe tehát ezt írjuk.

07:30

Itt van több példa bemenet és kimenet is.

z2f6be0 -> z2f6ki0

z2f6be1 -> z2f6ki1

z2f6be2 -> z2f6ki2

z2f6be3 -> z2f6ki3

z2f6be4 -> z2f6ki4

z2f6be5 -> z2f6ki5

Most nézzük a mintamegoldást erre a feladatra. A mintamegoldás forráskódját érdemes letölteni az z2f6.c fájlból, vagy benne van az archívban is.

Először definiálunk két függvényt, amik kiírnak, illetve beolvasnak egy időpontot. Az időpontokat egész számként, az éjfél óta eltelt percek számaként tároljuk ebben a programban, tehát például a 08:05 időpontot a 485 szám jelöli. Ezzel a reprezentációval egyszerűen tudunk számolni. A printtime függvény kiír egy időpontot, a scantime függvény pedig beolvas és visszaad egyet.

#include 
#include 

#define MAX_TRAIN 50

void
printtime(int t) {
	printf("%02d:%02d", t/60, t%60);
}

int
scantime() {
	int hour, min;
	if (2 != scanf("%d:%d", &hour, &min))
		exit(1);
	return 60 * hour + min;
}

Ezután következik a main függvény, ami először beolvassa az adatokat, majd kiszámolja és kiírja az eredményt. A buszok indulási és érkezési időpontját a train_depart és a train_arrive tömbbe tároljuk, a buszok számát pedig az ntrains tárolja. A latest_unboard_time változóba kiszámoljuk, mikor kell legkésőbb leszálni a buszról a célállomáson.

Ezután végigmegyünk a buszokon, és a board_time változóba mentjük az eddig látott legkésőbb időpontot, amikor fölszállhatunk a buszra. Azokat a buszokat kell számolni, amik nem érkeznek később a latest_unboard_time időpontnál, de később indulnak, mint az eddig talált legkorábbi indulás.

Végül a kapott időpontból levonjuk a séta hosszát az első megállóig, majd kiírjuk, ekkor kell elindulni.

int
train_depart[MAX_TRAIN],
train_arrive[MAX_TRAIN];

int
main(void) {
	int ntrains, k;
	int leave_time, first_walk, board_time,
		latest_unboard_time, second_walk, latest_arrive_time;
	if (1 != scanf("%d", &first_walk))
		exit(1);
	latest_arrive_time = scantime();
	if (1 != scanf("%d", &second_walk))
		exit(1);
	latest_unboard_time = latest_arrive_time - second_walk;
	if (1 != scanf("%d", &ntrains))
		exit(1);
	for (k = 0; k < ntrains; k++) {
		train_depart[k] = scantime();
		train_arrive[k] = scantime();
	}
	board_time = -10000;
	for (k = 0; k < ntrains; k++) {
		if (train_arrive[k] <= latest_unboard_time) {
			if (board_time < train_depart[k]) {
				board_time = train_depart[k];
			}
		}
	}
	leave_time = board_time - first_walk;
	printtime(leave_time);
	printf("\n");
	return 0;
}

Most nézzük a feladat igazi változatát. Ezúttal emberünk egy átszállással szeretne utazni. Kiválasztotta, hogy hol száll föl és le a két buszra (esetleg az egyik lehet vonat is). A bemenet mindkét busz menetrendjét megadja a megfelelő két állomás között.

A bemenet formája a következő. Az első sorban megint egy egész szám, egy időpont, majd egy harmadik egész szám található. Az első szám megadja, hány percet sétálni az első busz megállójáig az indulási helytől, a második szám azt, hány percet kell sétálni a második busz megállójától a célállomásig. Az első buszról emberünk ugyanott száll le, ahol a második buszra föl, ezért nulla idő alatt is át tud szállni. Az időpont megadja, mikor kell legkésőbb beérkeznie a célba.

A következő sorok az első megállóhelytől az átszállóhelyig közlekedő buszok menetrendjét adják meg ugyanúgy, mint az egyszerűbb feladatban: egy sor megadja a buszok számát (legfeljebb 50), a következő sorok a buszok indulási és érkezési időpontját a két megállóban. Végül az utolsó néhány sor hasonlóan adja meg az átszállóhelytől a célhoz közeli megállóig közlekedő buszok menetrendjét (ilyen buszból is legfeljebb 50 van).

Egy példa a következő.

8 17:24 8
5
09:32 09:41
19:21 19:31
05:33 05:41
20:39 20:48
06:34 06:43
5
20:51 23:10
09:47 11:49
15:43 17:55
06:35 09:13
13:17 15:16

Mivel 17:24-re kell legkésőbb megérkezni, és ez előtt 8 perc séta van, a második busz csak olyan lehet, ami 17:16 előtt megérkezik. Ennek az öt közül a második, negyedik, és ötödikként felsorolt busz felel meg. A legkésőbb az ötödik indul: erre 13:17-kor kell fölszállni. E szerint az első vonalon olyan buszt kell keresni, ami legkésőbb 13:17-re megérkezik. A felsorolásban az első, harmadik, és ötödik busz ilyen (sajnos mindenképpen nagyon sokat kell várakozni). A legjobb még az elsőnek felsorot busz, ami 09:32-kor indul, ehhez nyolc perccel előbb, 09:24-kor kell elindulni a szállásról a megállóba. A program tehát ezt írja ki.

09:24

Az átszállással bonyolított feladatot kell tehát megoldania, és feltöltenie a SIO rendszerre a z2f2 feladathoz. Érdemes lehet kiindulni az egyszerűbb változat kódjából.

Még néhány teszt adat a feladathoz.

z2f2be0 -> z2f2ki0

z2f2be1 -> z2f2ki1

z2f2be2 -> z2f2ki2

z2f2be3 -> z2f2ki3

z2f2be4 -> z2f2ki4

z2f2be5 -> z2f2ki5

3. feladat (z2f3): algebrai kifejezések kirajzolása: szebb hatványok

Ebben a feladatban algebrai kifejezéseket fogunk átalakítani prefix írásmódról hagyományos síkbeli matematikai jelölésre.

Algebrai kifejezésnek tekintjük a következőket: változó, kis szám konstans, két algebrai kifejezés hatványozása, hányadosa, szorzata, különbsége vagy összege.

A prefix írásmódban az operátor jelét az argumentumai elé írjuk. Ennek az az előnye, hogy a jelölés zárójelek nélkül is egyértelmű. Változónak tekintjük az angol ábécé kisbetűit, ezek prefix jelölésben is egyszerűen egy kisbetűbel vannak megadva. Hasonlóan a 0 és 9 közti számokat kis számkonstansnak tekintjük, ezeket a prefix kifejezésben egy számjegy jelzi. (Nagyobb számot nem írunk le.)

Ha x és y két algebrai kifejezés, prefix alakjuk X és Y, akkor értelmes kifejezést kapunk, ha az x-et az y-ik hatványra emeljük, ennek az xy hatványnak a prefix alakja ^XY. Hasonlóan az x/y hányados, az x*y szorzat, az x-y különbség és az x+y összeg prefix alakje rendre /XY, *XY, -XY, +XY.

Az algebrai kifejezéseknek definiáljuk a hagyományos jelölését is. Ez egy karakterekből álló táblázat, aminek a magasságán és szélességén és a tartalmán kívül még ki van jelölve az egyik sora alapsornak.

A változók és konstansok hagyományos írása egy 1 széles 1 magas táblázat (az alapsor egyértelmű), aminek egyetlen karaktere a megfelelő betű illetve számjegy.

Bonyolultabb kifejezések hagyományos alakját kisebb táblázatok összerakásával kapjuk meg. Ilyenkor az új táblázat pont akkora, hogy az összes összerakott táblázat éppen beleférjen. Ahova semmilyen táblázat nem kerül, ott szóközök állnak.

Hatványozásnál az alap és a kitevő hagyományos alakát úgy rakjuk össze, hogy a kitevőhöz tartozó táblázat bal alsó sarka épp összeérjen az alaphoz tartozó táblázat jobb felső sarkával. A hatvány hagyományos alakjának az alapsora az alap alapsora.

Ha egy algebrai kifejezés két részkifejezés hányadosa, akkor a két részkifejezés hagyományos alakja egymás alá kerül úgy, hogy egy sor kimarad köztük. Vízszintesen úgy kell igazítani a két résztáblázatot, hogy a közepük egymás fölött legyen, vagy, ha ez paritási okból lehetetlen, akkor a szélesebb táblázat közepe 1/2 karakterrel a keskenyebb közepe alatt legyen. A hányados táblázata jobb és bal oldalt is egy oszloppal szélesebb, mint a két rész közül a szélesebbik. Az alapsor az a sor, amelyik a két részkifejezés között szabadon maradt, ezt a sort a táblázatban végig mínusz [-] jelekkel kell feltölteni.

Ha egy algebrai kifejezés két részkifejezés összege, különbsége, vagy szorzata, akkor a két részkifejezés hagyományos alakját úgy kell összerakni, hogy a két rész alapsora ugyanabba a sorba essen, ez lesz az új kifejezés alapsora. Vízszintesen a két résztáblázat között szorzat esetén egy, összeg vagy különbség esetén három oszlopot ki kell hagyni. A szorzat esetén nincs műveleti jel; összegnél és különbségnél a három kihagyott oszlop közül a középsőnek és az alapsornak a metszetébe egy plusz [+] illetve mínusz [-] karaktert kell elhelyezni.

Mielőtt azonban összeraknánk a kifejezéseket, néha még zárójeleket kell beszúrni. Egy hagyományos alakot úgy kell bezárójelezni, hogy a táblázatot bal és jobb oldalt egy-egy oszloppal kibővítjük, ezeket a táblázat teljes magasságában bal [(] illetve jobb [)] zárójelekkel kell kitölteni. Az új táblázat alapsora ugyanaz, ahova a kisebb táblázat alapsora került. Egy kifejezés hagyományos alakját bizonyos esetekben ennek bezárójelezésével kell helyettesíteni, majd az eredeti táblázat helyett ezt használni nagyobb kifejezés építéséhez.

Pontosan a következő esetekben kell zárójelezni. Zárójelezni kell egy szorzatot, hatványt, vagy hányadost, ha egy hatvány alapjában áll; zárójelezni kell egy összeget vagy különbséget, ha ez egy szorzat tényezője vagy egy hatvány alapja. Olyan kifejezésekkel nem foglalkozunk, amiben egy szorzat jobb oldali tényezője szorzat, vagy ahol egy összeg vagy különbség jobb oldali tagja összeg vagy különbség.

Amikor kiírunk egy kifejezést hagyományos alakban, akkor a táblázatot soronként kell kiírni, a sorok végéről a szóközöket elhagyva, és minden sor után újsor jelet kell írni. Végül az egész kifejezés után még egy újsort ki kell írni.

Lássunk egy példát. A következő kifejezést nézzük.

(a/b)1/2 - (c + d)(1 + a) - (a - d)/b2 + b2 / (ac)

Ennek a prefix alakja a következő.

+--^/ab/12*+cd+1a/-ad^b2/^b2*ac

A hagyományos alakja egész kifejezésként kiírva pedig az alábbi.

      1
     ---
      2                                  2
( a )                         a - d     b
(---)    - (c + d) (1 + a) - ------- + -----
( b )                           2       a c
                               b

A feladatban a bemenet több sorból áll, minden sorban egy algebrai kifejezés prefix alakja van. A kimenetben ezeknek a kifejezéseknek a hagyományos alakját kell fölrajzolni.

Minden kifejezés olyan, hogy a prefix alakja legfeljebb 100 karakterből áll.

Néhány példa a következő.

z0f7be0 -> z0f7ki0

z0f7be1 -> z0f7ki1

z0f7be2 -> z0f7ki2

Ezt az egyszerűbb feladatot a z0f7.c program oldja meg. Íme a forráskód.

#include 
#include 
#include 

int
max(int x, int y) {
	return x < y ? y : x;
}

#define PLANBUF_LEN 512

#define PREC_ATOM 0
#define PREC_POWER 2
#define PREC_QUOTIENT 3
#define PREC_PRODUCT 4
#define PREC_SUM 5

struct
plan {
	int width;
	int height;
	int depth;
	int prec;
	int head;
	const struct plan *lhs;
	const struct plan *rhs;
} planbuf[PLANBUF_LEN];
int
plancnt;

void
init(void) {
	plancnt = 0;
}

struct plan *
allocplan(void) {
	struct plan *r;
	r = planbuf + plancnt++;
	if (PLANBUF_LEN < plancnt)
		exit(1);
	r->lhs = r->rhs = 0;
	r->prec = r->head = -1;
	r->width = r->height = r->depth = 32767;
	return r;
}

const struct plan *
parenthisize(const struct plan *l) {
	struct plan *p;
	p = allocplan();
	p->head = '(';
	p->lhs = l;
	p->width = l->width + 2;
	p->height = l->height;
	p->depth = l->depth;
	p->prec = PREC_ATOM;
	return p;
}

const struct plan *
layout(const char **formulap) {
	struct plan *p;
	char head;
	const struct plan *l, *r;
	p = allocplan();
	p->head = head = *(*formulap)++;
	if (isdigit(head) || islower(head)) {
		p->width = 1;
		p->height = 1;
		p->depth = 0;
		p->prec = PREC_ATOM;
	} else {
		l = layout(formulap);
		r = layout(formulap);
		if ('^' == head) {
			if (PREC_POWER <= l->prec)
				l = parenthisize(l);
			p->width = l->width + r->width;
			p->height = l->height + r->depth + r->height;
			p->depth = l->depth;
			p->prec = PREC_POWER;
		} else if ('/' == head) {
			p->width = 2 + max(l->width, r->width);
			p->height = 1 + l->height + l->depth;
			p->depth = r->height + r->depth;
			p->prec = PREC_QUOTIENT;
		} else if ('*' == head) {
			if (PREC_PRODUCT < l->prec)
				l = parenthisize(l);
			if (PREC_PRODUCT <= r->prec)
				r = parenthisize(r);
			p->width = 1 + l->width + r->width;
			p->height = max(l->height, r->height);
			p->depth = max(l->depth, r->depth);
			p->prec = PREC_PRODUCT;
		} else if ('+' == head || '-' == head) {
			if (PREC_SUM <= r->prec)
				r = parenthisize(r);
			p->width = 3 + l->width + r->width;
			p->height = max(l->height, r->height);
			p->depth = max(l->depth, r->depth);
			p->prec = PREC_SUM;
		} else 
			exit(1);
		p->lhs = l;
		p->rhs = r;
	}
	return p;
}

#define PAPER_WIDTH 500
#define PAPER_HEIGHT 100

char
paper[PAPER_HEIGHT][PAPER_WIDTH];
int
box_height, box_width;

void
erase(const struct plan *p) {
	int r, c;
	box_height = p->height + p->depth;
	box_width = p->width;
	if (PAPER_HEIGHT < box_height || PAPER_WIDTH < box_width)
		exit(1);
	for (r = 0; r < box_height; r++)
		for (c = 0; c < box_width; c++)
			paper[r][c] = ' ';
}

void
output(void) {
	int r, w, c;
	for (r = 0; r < box_height; r++) {
		w = box_width;
		while (' ' == paper[r][w - 1])
			w--;
		for (c = 0; c < w; c++)
			putchar(paper[r][c]);
		putchar('\n');
	}
	putchar('\n');
}

void
paint(const struct plan *p, int row, int col) {
	int r, c;
	int head;
	if (
		row - p->height + 1 < 0 || 
		box_height < row + p->depth ||
		col < 0 ||
		box_width < col + p->width
	)
		exit(1);
	head = p->head;
	switch (head) {
		case '(':
			for (r = row - p->height + 1; r <= row + p->depth; r++) {
				paper[r][col] = '(';
				paper[r][col + p->width - 1] = ')';
			}
			paint(p->lhs, row, 1 + col);
			break;
		case '^':
			paint(p->lhs, row, col);
			paint(p->rhs, row - p->lhs->height - p->rhs->depth, col + p->lhs->width);
			break;
		case '/':
			for (c = col; c < col + p->width; c++)
				paper[row][c] = '-';
			paint(p->lhs, row - 1 - p->lhs->depth, col + (p->width - p->lhs->width)/2);
			paint(p->rhs, row + p->rhs->height, col + (p->width - p->rhs->width)/2);
			break;
		case '*':
			paint(p->lhs, row, col);
			paint(p->rhs, row, 1 + col + p->lhs->width);
			break;
		case '+': case '-':
			paint(p->lhs, row, col);
			paper[row][1 + col + p->lhs->width] = head;
			paint(p->rhs, row, 3 + col + p->lhs->width);
			break;
		default:
			if (isdigit(head) || islower(head))
				paper[row][col] = head;
			else
				exit(1);
			break;
	}
}

void
paint_main(const struct plan *p) {
	paint(p, p->height - 1, 0);
}

#define FORMULA_LEN 512

int
main(void) {
	char formulabuf[FORMULA_LEN];
	const struct plan *p;
	const char *formula;
	while (fgets(formulabuf, FORMULA_LEN, stdin)) {
		formula = formulabuf;
		init();
		p = layout(&formula);
		if ('\n' != *formula)
			exit(1);
		erase(p);
		paint_main(p);
		output();
	}
	return 0;
}

A kód megértéséhez a következőt kell tudni. Minden kifejezést két lépésben dolgozunk fel.

Az első lépésben, amit a layout függvény hajt végre, értelmezzük a prefix kifejezést, és minden részkifejezéséhez csinálunk egy struct plan típusú leírót. A leíróban eltárolunk elég információt ahhoz, hogy a kifejezés visszaállítható legyen, erre szolgálnak a head, lhs, rhs mezők; valamint az ehhez tartozó hagyományos kifejezés méretét is eltároljuk: a width mező a tábla szélességét tartalmazza, a height mező a magasságot az alapsortól a táblázat tetejéig, a depth pedig a mélységet az alapsor alatt. A prec mezőt arra használjuk, hogy eldöntsük, kell-e zárójelezni a kifejezést, ha igen, akkor a zárójelezett változathoz egy újabb leírót hozunk létre.

A második fázisban a leírókat bejárva kirajzoljuk a kifejezéseket. A paper karakter-mátrixba kerül az eredmény: ennek elég nagy részét kitöltjük szóközökkel, majd rekurzívan hívjuk a paint eljárást, ami egy leírót és a részkifejezés alapsorának a bal szélső mezőjének pozícióját kapja argumentumként, és a paper mátrixra rajzol. Végül a mátrix alapján kiírjuk az eredményt.

Az ön feladata ezzel a programmal kapcsolatban, hogy kis mértékben módosítsa a hatványozás kirajzolását. A módosítás bizonyos esetekben alacsonyabbra rakja a kitevőt, mint az eredeti szabályok.

A szabály következő része változatlan: a hatványozásnál először is az alapot zárójelbe rakjuk, ha ez az alap egy összeg, különbség, szorzat, hányados, vagy hatvány. A kitevő táblázatát vízszintesen úgy helyezzük el, hogy a bal széle éppen érintse az alaphoz tartozó táblázat jobb szélét. A függőleges elhelyezés azonban változik. A kitevőhöz tartozó táblázatot vagy úgy kell elhelyezni, hogy a legalsó sora egy sorral az alap alapsoránál följebb kerüljön, vagy úgy, hogy a kitevő legfelső sora az alap legfelső sora mellé kerüljön. A két szabály közül azt kell alkalmazni, amelyik magasabbra helyezi a kitevőt. A hatvány táblázatának a mérete továbbra is a legszűkebb úgy, hogy az alap és a kitevő dobozát is teljesen tartalmazza, és az alapsora megegyezik az alaphoz tartozó táblázat alapsorával.

Lássunk egy példát. A ++++^-ab2^^8wx^/dhu^/1o/18^/^9wp3 prefix kifejezést az eredeti z0f7 program így rajzolja meg:

                                  1
                                 ---         3
               x        u         8    (  w )
       2   ( w)    ( d )    ( 1 )      ( 9  )
(a - b)  + (8 )  + (---)  + (---)    + (----)
                   ( h )    ( o )      ( p  )


Az elkészítendő programnak ellenben ezt kell kirajzolnia.

                                  1
                                 ---   (  w )3
       2   ( w)x   ( d )u   ( 1 ) 8    ( 9  )
(a - b)  + (8 )  + (---)  + (---)    + (----)
                   ( h )    ( o )      ( p  )

Ebben az összegben az első tag a legegyszerűbb, közönséges hatványozás, ezt az eredeti és a módosított szabályok is ugyanígy rajzolják. A második és harmadik tag olyan hatványokat mutat, amelyekben a módosított szabályok révén egy sorral lejjeb kerülhet a kitevő. A negyedik tag az új szabályok közül az első alkalmazását mutatja be: itt a kitevő alját az alapvonal fölé kell igazítani egy sorral (ha a kitevő tetejét igazítanánk az alap tetejéhez, akkor a kitevő túl alacsonyan lenne). Végül az ötödik tag éppen a második új szabályt mutatja be, mivel itt a kitevőt föl kell emelni úgy, hogy a teteje az alap tetejével egy magasságban legyen.

Sok más példa található még az alábbi példafájlokban, különösen a középsőben.

z2f3be0 -> z2f3ki0

z2f3be1 -> z2f3ki1

z2f3be2 -> z2f3ki2

A módosított feladat megoldását a SIO-ban a z2f3 feladatra kell feltölteni.