diff --git a/problem-set/FancyTribonacci.java b/problem-set/FancyTribonacci.java new file mode 100644 index 0000000..4fd3b01 --- /dev/null +++ b/problem-set/FancyTribonacci.java @@ -0,0 +1,250 @@ +import acm.program.ConsoleProgram; + +public class FancyTribonacci extends ConsoleProgram { + + /* The special symbol that breaks the input stream */ + private static final int DELIMITER = -1; + + public void run() { + // Receive first 3 elements of the sequence A + int A0 = readNumber("A", 0); + int A1 = readNumber("A", 1); + int A2 = readNumber("A", 2); + + // For debugging purposes: + printSequence(A0, A1, A2, 20); + + // Read the first element of the sequence B + int B0 = readNumber("B", 1); + + // Is the sequence A made of only zeros? + if(A0 == 0 && A1 == 0 && A2 == 0) { + solveZeroSpecialCase(B0); + } else { + solveProblem(A0, A1, A2, B0); + } + } + + /** + * Solves the special case when every member of A is 0 + * Precondition: A0 = A1 = A2 = 0 + */ + private void solveZeroSpecialCase(int currentElement) { + int currentIndex = 1; + boolean isResultPositive = true; + while(true) { + if(currentElement != 0) { + isResultPositive = false; + } + currentElement = readNumber("B", currentIndex); + if(currentElement == DELIMITER) { + break; + } + currentIndex++; + } + if(isResultPositive) { + println("Yes 0"); + } else { + println("No"); + } + } + + /** + * Precondition: A0, A1, A2 are not all zeros + */ + private void solveProblem(int A0, int A1, int A2, int B0) { + int B1 = readNumber("B", 1); + // The sequence B only consists of one element + if(B1 == DELIMITER) { + solveProblemForOneElement(A0, A1, A2, B0); + } else { + int B2 = readNumber("B", 2); + // The sequence B is a pair + if(B2 == DELIMITER) { + solveProblemForPair(A0, A1, A2, B0, B1); + } else { + // The sequence B is at least a triplet + solveExtendedProblem(A0, A1, A2, B0, B1, B2); + } + } + } + + /** + * Solves the problem when the length of the sequence B is 1 + */ + private void solveProblemForOneElement(int A0, int A1, int A2, int B0) { + int firstIndexOfElement = findFirstIndexOfElement(A0, A1, A2, B0); + if(firstIndexOfElement == -1) { + println("No"); + } else { + println("Yes " + firstIndexOfElement); + } + } + + /** + * Solves the problem when the length of the sequence B is 2 + */ + private void solveProblemForPair(int A0, int A1, int A2, int B0, int B1) { + int firstIndexOfPair = findFirstIndexOfPair(A0, A1, A2, B0, B1); + + if(firstIndexOfPair == -1) { + println("No"); + } else { + println("Yes " + firstIndexOfPair); + } + } + + /** + * Solves the problem when the length of the sequence B >= 3 + */ + private void solveExtendedProblem(int A0, int A1, int A2, int B0, int B1, int B2) { + // Now we have B0, B1, B2 + int indexOfTriplet = findIndexOfTriplet(A0, A1, A2, B0, B1, B2); + + boolean resultIsPositive = true; + if(indexOfTriplet == -1) { + resultIsPositive = false; + } + + int nextIndex = 3; + int numberOfElements = 3; + while(true) { + int nextElement = readNumber("B", nextIndex); + + if(nextElement == DELIMITER) { + break; + } + + int nextElementIndex = findNth(A0, A1, A2, indexOfTriplet + numberOfElements); + if(nextElementIndex != nextElement) { + resultIsPositive = false; + } + + numberOfElements++; + nextIndex++; + } + + if(resultIsPositive) { + println("Yes " + indexOfTriplet); + } else { + println("No"); + } + } + + /** + * Reads the number from the user + */ + private int readNumber(String sequenceName, int index) { + return readInt(sequenceName + index + ": "); + } + + /** + * Finds the Nth element of the given Tribonacci Sequence + * Precondition: A0, A1 and A2 should be passed as first 3 arguments + * Postcondition: Returns the Nth element of the given sequence + */ + private int findNth(int prevPrev, int prev, int current, int n) { + if(n == 0) { + return prevPrev; + } + if(n == 1) { + return prev; + } + if(n == 2) { + return current; + } + for(int currentIndex = 2; currentIndex < n; currentIndex++) { + int nextElement = prevPrev + prev + current; + prevPrev = prev; + prev = current; + current = nextElement; + } + return current; + } + + /** + * Finds where the first occurence of the element is located in the given sequence + * Precondition: len(B) == 1 + * Postcondition: Returns -1 if B is not a subsequence of A, otherwise returns n + * that satisfies A_n = B_0 + */ + private int findFirstIndexOfElement(int A0, int A1, int A2, int element) { + int currentIndex = 0; + while(true) { + int currentElement = findNth(A0, A1, A2, currentIndex); + if(element == currentElement) { + return currentIndex; + } + if(currentElement > element) { + break; + } + currentIndex++; + } + return -1; + } + + /** + * Finds where the first occurence of the pair is located in the given sequence + * Precondition: len(B) == 2 + * Postcondition: Returns -1 if B is not a subsequence of A, otherwise returns n + * that satisfies A_n = B_0 + */ + private int findFirstIndexOfPair(int A0, int A1, int A2, int firstElement, int secondElement) { + int currentIndex = 0; + while(true) { + int currentElement = findNth(A0, A1, A2, currentIndex); + int nextElement = findNth(A0, A1, A2, currentIndex + 1); + + if(firstElement == currentElement && nextElement == secondElement) { + return currentIndex; + } + + if(currentElement > secondElement) { + break; + } + + currentIndex++; + } + return -1; + } + + /** + * Finds where the subsequence B is located in the sequence A + * Precondition: len(B) >= 3 + * Postcondition: Returns -1 if B is not a subsequence of A, otherwise returns n + * that satisfies A_n = B_0 + */ + private int findIndexOfTriplet(int A0, int A1, int A2, int firstElement, int secondElement, int thirdElement) { + int currentIndex = 0; + while(true) { + int currentElement = findNth(A0, A1, A2, currentIndex); + int nextElement = findNth(A0, A1, A2, currentIndex + 1); + int nextNextElement = findNth(A0, A1, A2, currentIndex + 2); + + if(firstElement == currentElement && + nextElement == secondElement && + nextNextElement == thirdElement) + { + return currentIndex; + } + + if(currentElement > secondElement) { + break; + } + + currentIndex++; + } + return -1; + } + + /** + * For debugging purposes only, prints first numberOfElements members of A + */ + private void printSequence(int A0, int A1, int A2, int numberOfElements) { + for(int currentIndex = 0; currentIndex < numberOfElements; currentIndex++) { + print(findNth(A0, A1, A2, currentIndex) + " "); + } + println(); + } + +} diff --git a/problem-set/FancyTribonacci.md b/problem-set/FancyTribonacci.md new file mode 100644 index 0000000..764cd10 --- /dev/null +++ b/problem-set/FancyTribonacci.md @@ -0,0 +1,86 @@ +# FancyTribonacci + +## ამოცანა: +დაჩის ძალიან უყვარს ფაზლები და გადაწყვიტა შენი პროგრამირების ცოდნა თავისი ამოცანით შეამოწმოს. მან მოიფიქრა მიმდევრობა A შემდეგნაირად: + +1. $A_0$, $A_1$ და $A_2$ მიმდევრობის წევრები ხელით ჩამოწერა +1. $n$-ური წევრი კი ასე განსაზღვრა: $A_n = A_{n-1} + A_{n-2} + A_{n-3}$, როცა $n \geq 3$ + +მიმდევრობის ყველა წევრი არაუარყოფითი მთელი რიცხვია. + +დაჩი შენს პროგრამაში შემოიყვანს $A_0$, $A_1$ და $A_2$-ს და რაიმე $B$ მიმდევრობას, რომელიც მინიმუმ 1 წევრისგან შედგება. ანუ შეგძლიათ ჩათვალოთ, რომ მინიმუმ 4 რიცხვი შემოჰყავს შენს პროგრამაში და შემოყვანას მაშინ ასრულებს, როდესაც DELIMITER-ს ჩაწერს. + +შენ უნდა დაადგინო არის თუ არა $B$ $A$-ს ქვემიმდევრობა ანუ არსებობს თუ არა რაიმე $n \geq 0$, ისეთი რომ $A_n = B_0$, $A_{n+1} = B_1$ და ასე შემდეგ. + +დადებითი პასუხის შემთხვევაში უნდა დაპრინტოთ “Yes” და პირველი $n$-ის მნიშვნელობა, თუ B არ არის A-ს ქვემიმდევრობა მაშინ უნდა დაპრინტოთ “No”. + +**არ გაქვთ მასივებისა და მონაცემთა სტრუქტურების გამოყენების უფლება.** + +## მაგალითები: + +1. $A_0 = 0, A_1 =0, A_2 = 1 \newline B_0 = 1$ + + პასუხი: Yes 1 + +1. $A_0 = 0, A_1 =0, A_2 = 1 \newline B_0 = 3$ + + პასუხი: No +1. $A_0 = 1, A_1 = 1, A_2 = 1 \newline B_0 = 1, B_1 = 3$ + + პასუხი: Yes 2 +1. $A_0 = 0, A_1 = 0, A_2 = 0 \newline B_0 = 0, B_1 = 0, B_3 = 0, B_4 = 0$ + + პასუხი: Yes 0 + +## ამოცანის ამოხსნის გზა: + +ამოცანის ამოსახსნელად თავიდან უნდა ავაგოთ დამხრარე ფუნქცია $findNth(A_0, A_1, A_2, n)$, რომელსაც გადაეცემა მიმდევრობის პირველი სამი წევრი და $n$ და გვიბრუნებს $A_n$-ს. + +ამ ფუნქციაში ცალკე უნდა განვიხილოთ $n = 0, 1, 2$ შემთხვევები და დავაბრუნოთ შესაბამისი მნიშვნელობა. თუ $n \geq 3$, for-ციკლის დახმარებით უნდა ვიპოვოთ $A_n$. ციკლის თითოეულ იტერაციაზე უბრალოდ დავითვლით $nextElement = A_{currentIndex} + A_{currentIndex-1} + A_{currentIndex-2}$, გადავაჩოჩებთ შენახულ მნიშვნელობებს ერთით: $A_{currentIndex-2}$-ის ადგილას ჩაიწერება $A_{currentIndex-1}$, $A_{currrentIndex-1}$-ის ადგილას ჩაიწერება $A_{currentIndex}$ და $A_{currentIndex}$-ის ადგილას ჩაიწერება $nextElement$. როდესაც ციკლი დასრულდება, პასუხი იქნება $A_{currentIndex}$-ში. + +ამოცანის ამოსახსნელად უნდა განვიხილოთ 4 განსხვავებული შემთხვევა: +1. $A_0 = A_1 = A_2 = 0$ + + თუ სრულდება პირობა: $\forall i < length(B) : B_i = 0$, მაშინ პასუხად უნდა დავპრინტოთ "Yes 0" + + თუ $\exists i < length(B) : B_i \neq 0$, მაშინ პასუხია "No" + +1. $length(B) = 1$ + + ამ შემთხვევაში უნდა ვიპოვოთ პირველი $A_n$, რომელიც მოცემული $B_0$-ის ტოლია. ამის გაკეთება შესაძლებელია მარტივი while ციკლითა და $findNth()$ მეთოდის დახმარებით. თავიდან უნდა შემოვიღოთ ცვლადი $currentIndex = 0$ და ყველა $A_{currentIndex}$-ის მნიშვნელობა შევადაროთ $B_0$-ს. თუ $A_{currentIndex} = B_0$, მაშინ დავპრინტავთ "Yes"-სა და $currentIndex$-ს, თუ $A_{currentIndex} > B_0$ მაშინ $B_0$ მიმდევრობაში არ არსებობს და დავპრინტავთ "No"-ს. ეს იქიდან ვიცით, რომ მიმდევრობის ყველა წევრი არაუარყოფითია და შესაბამისად $A_{n+1} = A_n + A_{n-1} + A_{n-2} \geq A_n$. + + თუ $A = 0, 1, 0, 1, 2, 3...$ და $B = 1$ + პასუხი უნდა იყოს "Yes 1" + +1. $length(B) = 2$ + + ამ შემთხვევაში უნდა ვიპოვოთ პირველი $A_n$, რომლისთვისაც სრულდება პირობა $A_n = B_0$ და $A_{n+1} = B_1$. მსგავსად, while ციკლის დახმარებით ვიპოვი პირველ $currentIndex$-ს, რომელიც ამ ორ მოთხოვნას აკმაყოფილებს. თუ რომელიმე ეტაპზე მივიღებ $A_{currentIndex} > B_1$, ეს იმას ნიშნავს, რომ $B$ არ არის $A$-ს ქვემიმდევრობა. თუ $A_{currentIndex}$-მა $B$ მიმდევრობის ორივე წევრს გადაუსწრო, არაუარყოფითობის გამო $A$-ს არც ერთი ელემენტი $B$-ს წევრებს ვეღარ გაუტოლდება. ამ მსჯელობაში იმ დაშვებასაც ვიყენებ, რომ თუ $B$ $A$-ს ქვემიმდევრობაა, მაშინ $B_0 \leq B_1$. + + თუ $A = 1, 1, 1, 3, 5, 9...$ და $B = 1, 1$ + პასუხი უნდა იყოს "Yes 0" + + თუ $A = 1, 1, 1, 3, 5, 9...$ და $B = 1, 3$ + პასუხი უნდა იყოს "Yes 2" + +1. $length(B) \geq 3$ + წინა ორი შემთხვევის ცალ-ცალკე განხილვა იმიტომ მომიწია, რომ საშიშროება იყო ერთეული წევრები და წყვილები მიმდევრობაში გამეორებულიყო, მაგალითად, როგორც მოხდა $A = 1, 1, 1, 3, 5, 9...$ შემთხვევაში. მაგრამ თუ მაქვს უკვე B მიმდევრობის 3 ან მეტი წევრი მოცემული, გარანტია მაქვს, რომ A-ში იგი ქვემიმდევრობად ან ერთხელ შემხვდება ან საერთოდ არ შემხვდება. + + თუ $m > n + 2$, მაშინ ვიცი, რომ $A_n \leq A_{n+1} \leq A_{n+2} \leq A_m \leq A_{m+1} \leq A_{m+2}$, თუ დავუშვებთ, რომ $A_n = A_m = B_0$, გამოგვივა, რომ $A_n = A_{n+1} = A_{n+2} = A_m = A_{m+1} = A_{m+2} = 0$, რაც უკვე განვიხილეთ. + + გადავამოწმოთ ასევე თანაკვეთის შემთხვევები, რათა დავრწმუნდეთ: + + 1. $A_{n+2} = A_m$ + + $A_n = B_0, A_{n+1} = B_1, A_{n+2} = A_m = A_n = B_0 = B_2, A_{m+1} = B_1, A_{m+2} = B_2 = B_0$ + + $A = ... B_0, B_1, B_0, B_1, B_0 ... $ + + $B_1 = B_0 + B_1 + B_0 \rightarrow B_0 = B_1 = B_2 = 0$ + + 1. $A_{n+1} = A_m$ + + $A_n = B_0, A_{n+1} = A_m = B_0, A_{n+2} = A_{m+1} = B_1, A_{m+2} = B_2$ + + $A = ... B_0, B_0, B_0, B_0 ... \rightarrow B_0 = 0$ + + აქედან გამომდინარე, შეგვიძლია, რომ თავიდან ვიპოვოთ $B$-ს პირველი სამი წევრის ადგილიმდებარეობა $A$ მიმდევრობაში, დავიმახსოვროთ $n$, რომლისთვისაც $A_n = B_0$ და შემდეგ $B_i$-დან დაწყებული($i \geq 3$) სათითაოდ შევადაროთ $A_{n+i}$-ს. \ No newline at end of file