Skip to content

Commit 12e15a2

Browse files
authored
Merge pull request #277 from pdp-archive/37_mergegame
Add 37-mergegame solution
2 parents 0247917 + 5b7f090 commit 12e15a2

File tree

6 files changed

+177
-4
lines changed

6 files changed

+177
-4
lines changed

_data/contests/37-PDP.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ mergegame:
7676
statement_pdf_url: "https://drive.google.com/file/d/1tFvY_3m4KCz4ldE3vyLLsidE-PYt1hQs/view"
7777
statement_md: true
7878
testcases_url: ""
79-
solution: false
79+
solution: true
8080
solution_author: ""
81-
codes_in_git: false
82-
solution_tags: []
81+
codes_in_git: true
82+
solution_tags: [greedy, stack, segment tree]
8383
on_judge: false
8484

8585
tiphunting:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
TASK(
2+
name = "mergegame",
3+
test_count = 52,
4+
files_dir = "testdata/37-PDP/mergegame/",
5+
input_file = "mergegame.in",
6+
output_file = "mergegame.out",
7+
time_limit = 1,
8+
mem_limit = 64,
9+
solutions = [
10+
SOLUTION(
11+
name = "mergegame_n2",
12+
source = "mergegame_n2.cc",
13+
passes_all_except_for = [2, 11, 12, 38, 39, 40, 42, 43, 44, 46, 47],
14+
lang = "c++",
15+
),
16+
SOLUTION(
17+
name = "mergegame_stack",
18+
source = "mergegame_stack.cc",
19+
passes_all,
20+
lang = "c++",
21+
),
22+
]
23+
)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <algorithm>
2+
#include <cstdio>
3+
4+
const long MAXN = 100'000;
5+
// Η μέγιστη δυνατή τιμή είναι η αρχική τιμή + log2 N.
6+
const long MAXV = 1e9 + 17;
7+
8+
long A[MAXN];
9+
10+
int main() {
11+
FILE *fi = fopen("mergegame.in", "r");
12+
long S, N;
13+
fscanf(fi, "%ld%ld", &S, &N);
14+
15+
long smallest = MAXV;
16+
for (long i = 0; i < N; ++i) {
17+
fscanf(fi, "%ld", &A[i]);
18+
smallest = std::min(smallest, A[i]);
19+
}
20+
fclose(fi);
21+
22+
long j = N;
23+
// Σε κάθε γύρο βρίσκουμε το μικρότερο στοιχείο smallest.
24+
// Αν υπάρχουν δύο διαδοχικά smallest τα ενώνουμε.
25+
// Διαφορετικά το αφαιρούμε.
26+
while (j > 1) {
27+
long j_prev = j;
28+
j = 0;
29+
long nxt_smallest = MAXV;
30+
for (long i = 0; i < j_prev; ++i) {
31+
if (A[i] == smallest) {
32+
// Αν το γειτονικό είναι ίσο, τα ενώνουμε.
33+
// Αλλιώς τα αφαιρούμε.
34+
if (i + 1 < j_prev && A[i] == A[i+1]) {
35+
A[j++] = smallest + 1;
36+
nxt_smallest = std::min(nxt_smallest, smallest + 1);
37+
++i;
38+
}
39+
} else {
40+
A[j++] = A[i];
41+
nxt_smallest = std::min(nxt_smallest, A[i]);
42+
}
43+
}
44+
smallest = nxt_smallest;
45+
}
46+
47+
FILE *fo = fopen("mergegame.out", "w");
48+
fprintf(fo, "%ld\n", smallest);
49+
fclose(fo);
50+
51+
return 0;
52+
}
53+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <cstdio>
2+
#include <vector>
3+
4+
int main() {
5+
FILE *fi = fopen("mergegame.in", "r");
6+
long S, N;
7+
fscanf(fi, "%ld%ld", &S, &N);
8+
9+
std::vector<long> st;
10+
for (long i = 0; i < N; ++i) {
11+
long cur;
12+
fscanf(fi, "%ld", &cur);
13+
while (!st.empty() && st.back() <= cur) {
14+
if (st.back() == cur) ++cur;
15+
st.pop_back();
16+
}
17+
st.push_back(cur);
18+
}
19+
20+
FILE *fo = fopen("mergegame.out", "w");
21+
fprintf(fo, "%ld\n", st[0]);
22+
fclose(fo);
23+
24+
return 0;
25+
}
26+
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
layout: solution
3+
codename: mergegame
4+
---
5+
6+
## Επεξήγηση εκφώνησης
7+
8+
Μας δίνεται ένας πίνακας $$N$$ στοιχείων και σε κάθε βήμα μπορούμε είτε να διαγράψουμε ένα στοιχείο είτε να συγχωνεύσουμε δύο ίδια στοιχεία $$\texttt{x x}$$ σε ένα: το $$\texttt{x+1}$$. Σκοπός είναι να μεγιστοποιήσουμε την τιμή του τελευταίου στοιχείου.
9+
10+
## Λύση σε $$\mathcal{O}(N^2)$$ χρόνο
11+
12+
Θα ξεκινήσουμε με έναν αλγόριθμο που σε κάθε γύρο, κάνει δύο ειδών κινήσεις που περιλαμβάνουν το μικρότερο στοιχείο $$\texttt{y}$$ του πίνακα:
13+
- **Κίνηση 1η:** Αν έχουμε $$\texttt{x y z}$$ με $$\texttt{x} \neq \texttt{y} \neq \texttt{z}$$, τότε διαγράφουμε το $$\texttt{y}$$.
14+
- **Κίνηση 2η:** Αν έχουμε $$k$$ συνεχόμενα $$y$$, δηλαδή $$\texttt{x y y \ldots y z}$$ με $$\texttt{x} \neq \texttt{y} \neq \texttt{z}$$, τότε τα συγχωνεύουμε από τα αριστερά προς τα δεξιά,
15+
<center>
16+
$$\texttt{x }\underbrace{\texttt{y+1 y+1 \ldots y+1}}_{k/2}\texttt{ z}$$.
17+
</center>
18+
Εφαρμόζοντας αυτές τις παρατηρήσεις στο παράδειγμα της εκφώνησης, βρίσκουμε την βέλτιστη τιμή:
19+
20+
$$
21+
\begin{aligned}
22+
& \texttt{ 1 {\color{red}{0}} 1 2 {\color{royalblue}{0 0}} 3} \to \\
23+
& \texttt{ {\color{royalblue}{1 1}} 2 {\color{red}{1}} 3} \to \\
24+
& \texttt{ {\color{royalblue}{2 2}} 3} \to \\
25+
& \texttt{ {\color{royalblue}{3 3}} } \to \\
26+
& \texttt{ 4 }.
27+
\end{aligned}$$
28+
29+
Διαισθητικά, ο λόγος που μπορούμε να κάνουμε τις κινήσεις του πρώτου είδους είναι ότι για να συγχωνεύσουμε το $$\texttt{y}$$, θα πρέπει να διαγράψουμε το $$\texttt{z}$$ ή το $$\texttt{x}$$ (ή μία μεγαλύτερη τιμή που έχει προκύψει από συγχώνευσή τους). Αλλά δεν μας συμφέρει να διαγράψουμε μία μεγαλύτερη τιμή και να κρατήσουμε μία μικρότερη.
30+
31+
Για τις κινήσεις του δεύτερου είδους, από την παραπάνω αιτιολόγηση δεν μας συμφέρει να δημιουργήσουμε τριάδες της μορφής $$\texttt{y+1 y y+1}$$, γιατί το $$\texttt{y}$$ θα διαγραφεί. Επομένως, μεγιστοποιούμε το πλήθος των $$\texttt{y+1}$$ και αν κάποια από αυτά είναι περιττά θα αφαιρεθούν στους επόμενους γύρους.
32+
33+
Πηγαίνοντας από τα αριστερά προς τα δεξιά μπορούμε να υλοποιήσουμε τις κινήσεις ενός γύρου σε γραμμικό χρόνο. Επίσης μπορούμε να χρησιμοποιώντας τον ίδιο πίνακα, καθώς το μέγεθος του μικραίνει, άρα αν έχουμε δυο δείκτες στον πίνακα αυτό, έναν για την ανάγνωση και ένα για την εγγραφή, δεν θα επικαλυφθούν. Αφού σε κάθε γύρο, φεύγει τουλάχιστον ένα στοιχείο, μπορεί να υπάρχουν το πολύ $$N - 1$$ γύροι. Άρα συνολικά ο αλγόριθμος χρειάζεται $$\mathcal{O}(N^2)$$ χρόνο.
34+
35+
Ο παρακάτω αλγόριθμος υλοποιεί αυτή την λύση και περνάει όλα τα testcases με $$N \leq 10.000$$.
36+
37+
{% include code.md solution_name='mergegame_n2.cc' %}
38+
39+
**Σημείωση:** Η μεγαλύτερη τιμή μπορεί να αυξηθεί το πολύ κατά $$\log_2 N + 1$$, επομένως οι τιμές χωράνε σε ``long``.
40+
41+
## Βέλτιστη λύση σε $$\mathcal{O}(N)$$ χρόνο
42+
43+
Για την βέλτιστη λύση, θα κρατάμε την ακολουθία σε φθίνουσα σειρά και θα προσθέτουμε τα στοιχεία ένα ένα από τα αριστερά προς τα δεξιά. Για κάθε στοιχείο που προσθέτουμε, θα αφαιρούμε όλα τα μικρότερα στοιχεία στα δεξιά της υπάρχουσας ακολουθίας. Έπειτα, όσο υπάρχουν ίσα στοιχεία θα κάνουμε συγχωνεύσεις. Με αυτόν τον τρόπο διατηρείται η μονοτονία της ακολουθίας.
44+
45+
Στο παράδειγμα της εκφώνησης, η διαδικασία έχει ως εξής:
46+
47+
$$
48+
\begin{aligned}
49+
& \texttt{ {\color{seagreen}{1}} } \to \\
50+
& \texttt{ 1 {\color{seagreen}{0}} } \to \\
51+
& \texttt{ 1 0 {\color{seagreen}{1}} } \to \\
52+
& \texttt{ 1 1 } \to \\
53+
& \texttt{ 2 } \to \\
54+
& \texttt{ 2 {\color{seagreen}{2}} } \to \\
55+
& \texttt{ 3 } \to \\
56+
& \texttt{ 3 {\color{seagreen}{0}} } \to \\
57+
& \texttt{ 3 0 {\color{seagreen}{0}} } \to \\
58+
& \texttt{ 3 1 } \to \\
59+
& \texttt{ 3 1 {\color{seagreen}{3}} } \to \\
60+
& \texttt{ 3 3 } \to \\
61+
& \texttt{ 4 }.
62+
\end{aligned}$$
63+
64+
Διαισθητικά, ο λόγος που αυτή η μέθοδος δουλεύει είναι παρόμοιος με τον λόγο της πρώτης μεθόδου.
65+
* Αν έχουμε $$\texttt{x} > \texttt{y} < \texttt{z}$$, τότε το $$\texttt{y}$$ δεν μας συμφέρει να χρησιμοποιηθεί γιατί πρώτα θα πρέπει να διαγραφεί ένα από τα γειτονικά του στοιχεία που είναι μεγαλύτερα.
66+
* Για τις συγχωνεύσεις που γίνονται, καθώς τις κάνουμε από αριστερά προς τα δεξιά, πάλι θα κάνουμε το μέγιστο πλήθος από αυτές .
67+
68+
Η παρακάτω υλοποίηση κρατάει την ακολουθία σε μία στοίβα. Κάθε στοιχείο θα μπει και θα βγει το πολύ μία φορά από την στοίβα. Επομένως, ο αλγόριθμος χρειάζεται $$\mathcal{O}(N)$$ χρόνο και περνάει όλα τα testcases.
69+
70+
{% include code.md solution_name='mergegame_stack.cc' %}
71+

contests/_37-PDP/b-mergegame-statement.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ codename: mergegame
6868
- $$0 \leq e_i \leq 1.000.000.000$$.
6969

7070
## Υποπροβλήματα:
71-
1. (9 βαθμοί) $$e1\leq e2\leq \dots \leq e_N$$, δηλαδή οι εκθέτες βρίσκονται σε αύξουσα σειρά
71+
1. (9 βαθμοί) $$e_1\leq e_2\leq \dots \leq e_N$$, δηλαδή οι εκθέτες βρίσκονται σε αύξουσα σειρά
7272
1. (11 βαθμοί) Για την επίτευξη της βέλτιστης απάντησης απαιτούνται είτε μόνο διαγραφές, είτε μόνο συγχωνεύσεις
7373
1. (16 βαθμοί) $$N\leq 500$$
7474
1. (19 βαθμοί) $$N\leq 10.000$$ και ο μέγιστος εκθέτης δεν υπερβαίνει το $$5.000$$

0 commit comments

Comments
 (0)