차근차근 알려드립니다!
목표에 따라 배우는 방식이나 배우는 속도 등이 다를 수 있습니다
예를 들어 게임 개발을 위해 배우는 사람은 게임과 관련되게 배우는 것이 좋겠지요?
크게 3가지 부류로 나누겠습니다
세가지 배우는 목표에 따라 공부 방법이 매우 다릅니다.
만약 이 셋에 모두 포함되지 않는다면 자신은 3번이라고 생각하시면 됩니다.
학교 시험에서의 코딩은 암기입니다.
문제를 많이 푸는것도 중요하지만 시험범위에 해당에 해당하는 개념들을 완전히 숙지해야 합니다.
다른거는 생각하지 마세요. 학교에서 나온 자료를 잘 활용하는 것이 중요합니다. 또, 기출문제를 푸는것도 매우 중요합니다. 학교에서 자주 내는 미끼 문제에 걸리지 않으려면 기출을 푸는 요령이 필요하니까요.
주로 게임을 개발하거나 웹사이트를 만드는 목적을 지니는 사람들인데요, 아래는 목적에 따른 주요 사용 언어입니다.
컴퓨터 프로그램: C++, C#, Java, Python
웹사이트: PHP, Javascript, Python
IOS 앱: Swift
안드로이드 앱: Kotlin, Java, C++
기본적으로 처음 시작할때부터 어려운 언어를 하면 조금 부담스러울 수 있기 때문에 코딩을 처음 배우는 단계로써 일단 Python을 추천합니다. 만약 조금 빨리 프로젝트를 하고 싶다는 마음이 있다면 목적에 맞는 언어를 선택해 주세요.
배울 언어는 선택했고, 그렇다면 어떻게 공부해야 하는지 알아봅시다.
‘Mimo’라는 앱을 다운받아서 자신이 배우고자 하는 언어의 코스가 있는지 확인합니다. 만약 없다면, ‘Sololearn’이라는 앱을 다운받아 배우고자 하는 코스를 선택해서 배우면 됩니다!
코딩 문제를 푸는데 가장 많이 사용되는 언어는 C++, Python입니다. 자세한 설명은 아래 있습니다.
Python:
C++:
저는 C++을 더 선호하기는 합니다.
파이썬을 먼저 배우고 C++을 나중에 배우는 옵션도 있으니, 참고해주세요!
‘Mimo’라는 앱을 다운받아서 자신이 배우고자 하는 언어의 코스가 있는지 확인합니다. 만약 없다면, ‘Sololearn’이라는 앱을 다운받아 배우고자 하는 코스를 선택해서 배우면 됩니다!
이제 문제를 푸는 연습을 할 것입니다! ‘백준’이라는 사이트에 들어가서 ’단계별로 풀어보기’에서 문제들을 차근차근 풀어봅니다! 모르는 문제를 인터넷에 검색하는 것을 두려워하지 마세요! 하지만 인터넷에 있는 코드를 그대로 배끼는 행위는 하면 안됩니다!
]]>SCC와 CCW를 간단하게 집에서 복습한 후, NYPC 대회장에 향했습니다.
사실 본선진출은 한 것도 기적이여서 딱히 바라는 건 없었고, ‘그냥 키보드나 받자’라는 마음가짐으로 대회장에 도착했습니다.
큰 어려움 없이 쉽게 해결하였습니다. 딱히 어려움 없이 쉽게 풀었습니다.
문제를 보자마자 국어로 가득하고, 왠지 구현일 것 같다는 느낌이 들어서 바로 문제만 읽고 스킵했습니다.
나중에 돌아와서 유파로 문제를 풀었는데 계속해서 부분점수만 나왔습니다.
사실 알고보니 버그 2개 때문에 문제가 발생하는 것이었습니다. 대회 종료 12분 전 정도에 버그를 발견하고 100점을 받았습니다.
약간 게임이론 스타일의 문제여서 그리디 아니면 그런디를 활용해야 한다고 생각했습니다.
햐지만 그런디를 활용하기에는 시간제한과 범위가 너무 빡쎄다고 판단하여 바로 그리디로 무리하게 도전하였습니다.
예를 들어 홀수 짝수를 이용하여 문제를 해결하려 하였지만 사실상 불가능하다고 판단하여 섭태를 53점까지만 긁으려는 전략을 짰습나다.
이전에 구해놓은 값들을 바탕으로 한번만 계산하면 되기 때문에 시간복잡도가 회기적으로 줄어들 수 있었습니다. 결과적으로 O(n ^ 3)의 풀이를 하며 53점을 획득하였습니다.
모르겠습니다.
4번문제는 섭태를 긁기도 난이도가 어려운 듯하여 바로 포기했습니다.
문제를 잘못 이해하여 뻘짓을 하다가 제가 이해한 문제보다 훨씬 어려운 문제임을 깨닫게 되고 바로 포기했습니다.
3점만 섭태로 따고 마무리하였습니다.
놀랍게도 은상을 수상하였습니다.
시상식에서 저처럼 너풀너풀 다니는 사람이 없는 것이 살짝 의외였지만 다 내향인인 것 같았습니다.
사진을 다다스님과 함께 찍었는데, 그때 당시에는 제가 다다스님인줄 모르는 상태여서 나중에 알게 되었습니다.
상품도 아주 푸짐하여 좋네요.
2번 문제에서 심각하게 말려서 더 높은 상을 수상하지 못한 것이 아쉽긴 하나, 만족하는 ‘은상’이라는 결과를 얻어 기쁩니다.
만약 2번 문제에서 뻘짓하지 않았더라면 5번 문제에서 섭태를 28에서 50점까지도 긁지 않았을까 싶습니다.
아무튼 기분이 매우 좋고, 평소에 블로그에 글을 잘 올리지 않음에도 불구하고 너무나도 만족하는 결과를 얻어 자랑(?)하는 느낌으로 올리게 되었습니다.
지금까지 seunggialt였습니다. 감사합니다!
]]>https://www.acmicpc.net/problem/3015
개인적으로 이 문제를 해결하는 과정에서 어려움을 겪었습니다.
아무리 생각해도 문제를 해결하기 위한 감도 잡히지 않아 인터넷 해설을 참고하여 해결한 문제입니다.
그리디 스타일의 스택 활용 문제로, 스택을 활용하여 최적화를 해야 합니다.
만약 A라는 사람의 키가 B라는 앞 사람의 키보다 크다면, A뒤에 있는 사람들은 B를 볼 수 없게 됩니다.
다른 말로, A 사람이 마지막으로 B 사람을 볼 수 있는 것입니다.
이 특성을 활용하여, 앞 사람의 키가 현재 탐색하고 있는 사람의 키보다 작다면 앞 사람을 지울 수 있습니다.
또, 만약 A와 B가 서로를 볼 수 있다면, A와 키가 같은 모든 사람을 볼 수 있습니다.
이를 활용하여 연속되어 있는 같은 키의 갯수를 새주어, 효과적으로 표현하는 것입니다.
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
int main(void) {
ll n;
cin >> n;
ll ans = 0;
stack<pair<ll, ll>> s;
while (n--) {
ll x, cnt = 1;
cin >> x;
while (!s.empty() && x >= s.top().first) {
ans += s.top().second;
if (s.top().first == x) cnt = s.top().second + 1;
s.pop();
}
if (!s.empty()) ans++;
s.push({x, cnt});
}
}
https://www.acmicpc.net/problem/2618
DP와 역추적을 이용하는 문제로, 두 경찰차의 위치와 그 비용을 저장하는 방식의 DP를 사용할 수 있습니다.
역추적 문제라는 것이 훤하게 드러나기 때문에 2차원 배열로 DP를 구성하는 것이 가장 적절하다고 판단하였습니다.
두 경찰차가 사건으로 이동하는 경우의 수가 2의 거듭제곱 만큼 증가할 것이라 예상하고 완전이진 트리의 모습을 그릴 것이라 예상하였지만,
사실 도달하는 위치가 같은 좌표에서는 비용이 적은 곳을 선택하면 되기 때문에 파스칼의 삼각형(?)의 모양처럼 뻗어나가는 모습을 볼 수 있었습니다.
물론 파스칼의 삼각형과 아무런 연관이 없음으로, 그냥 2차원 배열에 데이터를 집어넣는다는 생각으로 공식을 유도했습니다.
설명한 바를 공식으로 유도하면 아래와 같습니다.
$f(x, y) = max(f(x - 1, y) + \alpha, f(x, y - 1) + \beta)$
참고로 $\alpha$와 $\beta$는 각각의 경찰차와 사건 장소 사이의 거리입니다.
뭔가 그럴싸해 보이는 점화식이지만, 아직 수정해야 할 부분이 있습니다.
바로 사건이 두 인덱스 $x$와 $y$에서 겹친다는 것입니다.
예를 들어 1번 사건이 $x$에서 한번 진행되고, 나중에 $y - 1$에서 한번 진행하는 등, 중복을 배제해야 합니다.
그렇기 때문에 실제로는 $x$ 값이 갱신할 때, $x + 1$로 갱신하는 것이 아닌, $max(x, y) + 1$로 갱신해야 하며,
$y$도 마찬가지입니다.
보통 역추적 문제에서는 $x - 1$이랑 $y - 1$을 비교하는 것이 가장 일반적이지만, $x$ 값과 $y$ 값이 어떻게 갱신될지 모르기 때문에 부모 정보를 따로 저장해야 합니다.
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
typedef struct police {
ll x1, x2, y1, y2;
ll ans = LLONG_MAX;
ll bef;
pair<ll, ll> par;
}police;
int main(void) {
ios::sync_with_stdio(0);
ll n;
cin >> n;
ll w;
cin >> w;
vector<ll> a(w + 1);
vector<ll> b(w + 1);
for (ll i = 1; i <= w; i++) {
cin >> a[i] >> b[i];
}
vector<vector<police>> cache(w + 1, vector<police>(w + 1));
police tmp;
tmp.x1 = 1; tmp.y1 = 1; tmp.x2 = n; tmp.y2 = n; tmp.ans = 0;
cache[0][0] = tmp;
ll _max = LLONG_MAX;
ll x, y;
for (ll i = 0; i <= w; i++) {
for (ll j = 0; j <= w; j++) {
ll k = max(i, j) + 1;
if (k > w || cache[i][j].ans == LLONG_MAX) continue;
ll cost = abs(a[k] - cache[i][j].x1);
cost += abs(b[k] - cache[i][j].y1);
cost += cache[i][j].ans;
if (cache[k][j].ans > cost) {
cache[k][j].bef = 1;
cache[k][j].ans = cost;
cache[k][j].x1 = a[k];
cache[k][j].y1 = b[k];
cache[k][j].x2 = cache[i][j].x2;
cache[k][j].y2 = cache[i][j].y2;
cache[k][j].par = {i, j};
}
if (k == w && _max > cache[k][j].ans) {
_max = cache[k][j].ans;
x = k;
y = j;
}
cost = abs(a[k] - cache[i][j].x2);
cost += abs(b[k] - cache[i][j].y2);
cost += cache[i][j].ans;
if (cache[i][k].ans > cost) {
cache[i][k].bef = 2;
cache[i][k].ans = cost;
cache[i][k].x1 = cache[i][j].x1;
cache[i][k].y1 = cache[i][j].y1;
cache[i][k].x2 = a[k];
cache[i][k].y2 = b[k];
cache[i][k].par = {i, j};
}
if (k == w && _max > cache[i][k].ans) {
_max = cache[i][k].ans;
x = i;
y = k;
}
}
}
cout << _max << "\n";
vector<ll> res(w);
for (ll i = w - 1; i >= 0; i--) {
res[i] = cache[x][y].bef;
ll alp = x;
ll beta = y;
x = cache[alp][beta].par.first;
y = cache[alp][beta].par.second;
}
for (ll i = 0; i < w; i++) {
cout << res[i] << "\n";
}
}
https://www.acmicpc.net/problem/17420
$N$의 범위가 작기 때문에 $O(N)$의 시간복잡도로 문제를 풀 것이다.
정우는 기한이 가장 적게 남은 기프티콘만을 사용할 수 있다.
다른 말로, $B_i$가 가장 작을 때의 $i$는 $A_i$도 가장 작다.
또, $B_i$가 가장 큰 값일 때의 $i$는 $B_i$도 가장 크다.
이러한 성질 때문에, $B$를 오름차순 정렬했을때 대응되는 위치의 $A_i$
값도 오름차순 정렬이 되어 있어야 한다.
하지만, $A_i$의 크기가 $B_i$의 크기보다 커야 정우가 원하는 일자에 기프티콘을 사용할 수 있기 때문에 $A_i$ ≥ $B_i$ 이 성립한다.
최종적으로 정리하자면, $B$를 오름차순 정렬하고 각 위치에 대응하는 곳에 $A_i$ 값들을 배치하면
아래의 두 식이 성립한다.
$A_i$ ≥ $B_i$
$A_{i - 1}$ ≤ $A_i$
만약 $B_{i - 1}$과 $B_i$ 값이 같다면 굳이 $A_{i - 1}$ ≤ $A_i$ 식이 성립할 필요가 없다.
그렇기 때문에, $B_i$ 보다 작은 값을 가지고 있는 최댓값을 $B$에서 찾은 후, 두 $A$ 값들을 비교해주면 된다.
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
int main(void) {
ios::sync_with_stdio(0);
ll n;
cin >> n;
vector<pair<ll, ll>> arr(n);
for (ll i = 0; i < n; i++) {
cin >> arr[i].second;
}
for (ll i = 0; i < n; i++) {
cin >> arr[i].first;
}
sort(arr.begin(), arr.end());
ll bef = ((max((ll)0, arr[0].first - arr[0].second + 29)) / 30) * 30 + arr[0].second;
ll cnt = (max((ll)0, arr[0].first - arr[0].second + 29)) / 30;
ll cache = 0;
for (ll i = 1; i < n; i++) {
ll tmp = (max((ll)0, max(bef, arr[i].first) - arr[i].second + 29)) / 30;
cnt += tmp;
if (i != n - 1 && arr[i + 1].first == arr[i].first) {
cache = max(cache, tmp * 30 + arr[i].second);
continue;
}
bef = tmp * 30 + arr[i].second;
bef = max(bef, cache);
}
cout << cnt;
}
https://www.jetbrains.com/idea/download/?section=windows
위 링크를 통해 IntelliJ Community Edition을 다운받는다. (페이지 아래로 스크롤하면 있다!) 인터넷에 검색하면 사용법이 많이 나와 있으니, 이 글에서는 사용법을 생략한다.
IntelliJ에서 새로운 Java 프로젝트를 만들고 샘플 코드 추가를 활성화한다. 자동으로 Main.java 파일이 생성될 것이다.
이제부터 public static void main(String[] args)의 두 중괄호 { } 사이에 코드를 작성할 것이다. 현재 중괄호 안에 있는 코드를 지우고, 아래 코드를 입력한다.
System.out.print("Hello, world");
그 후, 상단에 있는 초록색 화살표 버튼을 눌러 코드를 실행한다.
출력:
Hello, world
Java에서는 System.out.print()를 이용해 화면에 글자를 출력한다. 출력하고자 하는 내용을 소괄호 안에 넣으면 된다.
System.out.print() : 값을 출력하고 줄바꿈을 하지 않음System.out.println() : 값을 출력하고 줄바꿈을 함예를 들어 보겠다.
코드:
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
System.out.print("Hello ");
System.out.print("world!");
}
}
출력:
Hello world!
Hello world!
소괄호 안에서 사칙연산(+, -, *, /)을 하는 것도 가능하다.
코드:
public class Main {
public static void main(String[] args) {
System.out.print("1 + 1 = ");
System.out.print(1 + 1);
}
}
출력:
1 + 1 = 2
코딩을 하다 보면 정보를 저장해야 하는 경우가 있는데, 그런 정보들을 저장할 수 있는 공간을 변수라고 한다.
변수를 사용하려면 먼저 변수를 선언해야 한다.
변수타입 변수이름;
실생활에 비유하여 설명하겠다. 물건들을 상자에 저장한다고 생각해보자.
Java의 변수도 마찬가지다:
정수(integer)를 저장하는 변수는 int 타입을 사용한다.
int 변수이름;
변수에 값을 저장하거나 변경하려면:
변수이름 = 저장할값;
예시 코드:
public class Main {
public static void main(String[] args) {
int x = 5;
System.out.println(x);
x = 3;
System.out.print(x);
}
}
출력:
5
3
예시 코드:
public class Main {
public static void main(String[] args) {
int x = 4;
int y = 2;
System.out.println(x + y);
System.out.println(x - y);
System.out.println(x * y);
System.out.println(x / y);
}
}
출력:
6
2
8
2
가장 많이 사용되는 변수 타입들에 대해 알아보자.
| 타입 | 설명 | 비고 |
|---|---|---|
int, long |
정수 저장 | 20억 초과 시 long 사용 |
float, double |
실수(소수) 저장 | float은 7자리, double은 15자리 정밀도 |
char |
문자 하나 저장 | 작은따옴표 사용: ‘a’ |
String |
여러 문자(문자열) 저장 | 큰따옴표 사용: “hello” |
변수 타입에 따라 값에 접미사가 붙는 경우가 있다. 이것은 암기가 필수다!
| 타입 | 접미사 | 예시 |
|---|---|---|
| long | L 또는 l |
100L |
| 8진수 | 0 |
077 |
| 16진수 | 0x 또는 0X |
0xFF |
| float | f 또는 F |
3.14f |
| double | d 또는 D |
3.14d (생략 가능) |
참고: 큰 숫자는 가독성을 위해 중간에 _(언더스코어)를 넣을 수 있다.
int million = 1_000_000;
각 타입별 실제 사용 예시:
| 타입 | 예시 | 비고 |
|---|---|---|
| 논리형 | false, true |
접미사 없음 |
| 정수형 | 12, 100L |
L 접미사는 long |
| 실수형 | 3.14, 3.0f |
f는 float, 없으면 double |
| 문자형 | 'a', 'A' |
작은따옴표, 문자 1개 |
| 문자열 | "AB", "hello" |
큰따옴표, 여러 문자 |
참고: 논리형(boolean)은 조건문에서 다루므로 지금은 참고만 하자.
지금까지는 정보를 출력하는 방법을 배웠다. 이제는 사용자로부터 정보를 입력받는 방법을 알아보겠다.
Java에서 입력을 받으려면 Scanner라는 클래스를 사용해야 한다. 먼저 코드 맨 위에 다음과 같이 작성한다.
import java.util.Scanner;
그 다음, main 메소드 안에서 Scanner 객체를 생성한다.
Scanner sc = new Scanner(System.in);
Scanner를 사용하면 다양한 타입의 데이터를 입력받을 수 있다.
sc.nextInt() : 정수 입력받기sc.nextLong() : long 타입 정수 입력받기sc.nextFloat() : float 타입 실수 입력받기sc.nextDouble() : double 타입 실수 입력받기sc.next() : 공백 전까지의 문자열 입력받기sc.nextLine() : 한 줄 전체를 문자열로 입력받기예시 코드:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("정수를 입력하세요: ");
int num = sc.nextInt();
System.out.println("입력한 정수: " + num);
System.out.print("이름을 입력하세요: ");
String name = sc.next();
System.out.println("안녕하세요, " + name + "님!");
}
}
출력 예시:
정수를 입력하세요: 10
입력한 정수: 10
이름을 입력하세요: 홍길동
안녕하세요, 홍길동님!
nextLine()을 사용할 때는 주의가 필요하다. 다른 next 메소드들을 사용한 후에 nextLine()을 사용하면, 남아있는 개행문자(\n)를 읽어버려 빈 문자열이 입력될 수 있다.
예시:
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
sc.nextLine(); // 버퍼에 남은 개행문자 제거
String str = sc.nextLine();
프로그래밍을 하다 보면 특정 조건에 따라 다른 동작을 해야 하는 경우가 많다. 이럴 때 사용하는 것이 조건문이다.
if (조건) {
// 조건이 참일 때 실행되는 코드
}
예시 코드:
public class Main {
public static void main(String[] args) {
int score = 85;
if (score >= 80) {
System.out.println("합격입니다!");
}
}
}
출력:
합격입니다!
if (조건) {
// 조건이 참일 때 실행
} else {
// 조건이 거짓일 때 실행
}
예시 코드:
public class Main {
public static void main(String[] args) {
int age = 15;
if (age >= 18) {
System.out.println("성인입니다.");
} else {
System.out.println("미성년자입니다.");
}
}
}
출력:
미성년자입니다.
여러 조건을 검사해야 할 때 사용한다.
if (조건1) {
// 조건1이 참일 때
} else if (조건2) {
// 조건2가 참일 때
} else if (조건3) {
// 조건3이 참일 때
} else {
// 모든 조건이 거짓일 때
}
예시 코드:
public class Main {
public static void main(String[] args) {
int score = 85;
if (score >= 90) {
System.out.println("A학점");
} else if (score >= 80) {
System.out.println("B학점");
} else if (score >= 70) {
System.out.println("C학점");
} else {
System.out.println("F학점");
}
}
}
출력:
B학점
조건문에서 사용되는 주요 비교 연산자들:
== : 같다!= : 같지 않다> : 크다< : 작다>= : 크거나 같다<= : 작거나 같다여러 조건을 조합할 때 사용:
&& : AND (그리고) - 모든 조건이 참이어야 함|| : OR (또는) - 하나라도 참이면 됨! : NOT (부정) - 참을 거짓으로, 거짓을 참으로예시 코드:
public class Main {
public static void main(String[] args) {
int age = 20;
boolean hasLicense = true;
if (age >= 18 && hasLicense) {
System.out.println("운전할 수 있습니다.");
} else {
System.out.println("운전할 수 없습니다.");
}
}
}
출력:
운전할 수 있습니다.
같은 작업을 여러 번 반복해야 할 때 반복문을 사용한다.
정해진 횟수만큼 반복할 때 주로 사용한다.
for (초기식; 조건식; 증감식) {
// 반복할 코드
}
예시 코드:
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
System.out.println(i + "번째 반복");
}
}
}
출력:
1번째 반복
2번째 반복
3번째 반복
4번째 반복
5번째 반복
조건이 참인 동안 계속 반복한다.
while (조건) {
// 반복할 코드
}
예시 코드:
public class Main {
public static void main(String[] args) {
int count = 1;
while (count <= 5) {
System.out.println("카운트: " + count);
count++;
}
}
}
출력:
카운트: 1
카운트: 2
카운트: 3
카운트: 4
카운트: 5
최소 한 번은 실행하고, 그 후 조건을 검사한다.
do {
// 반복할 코드
} while (조건);
예시 코드:
public class Main {
public static void main(String[] args) {
int num = 10;
do {
System.out.println("숫자: " + num);
num--;
} while (num > 5);
}
}
출력:
숫자: 10
숫자: 9
숫자: 8
숫자: 7
숫자: 6
break : 반복문을 즉시 종료continue : 현재 반복을 건너뛰고 다음 반복으로예시 코드:
public class Main {
public static void main(String[] args) {
// break 예시
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
System.out.println(i);
}
System.out.println("---");
// continue 예시
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue;
}
System.out.println(i);
}
}
}
출력:
1
2
3
4
---
1
2
4
5
다음 내용에서는 배열, 메소드, 클래스와 객체 등 더 심화된 내용을 다룰 예정이다.
C++ 메인으로 코딩을 하다 보니, 실수를 하는 경우가 많다. 만약 오류 제보를 하고 싶다면 디스코드 아이디 _wha7y_를 통해 제보하시길 바란다.
]]>생각보다 어려운 문제였다. 하지만, 식정리를 하면 깔끔하게 풀리는 문제였다. 대강 2024 NYPC round 2-a을 더 간단화한 버전이라고 보는 것이 가능하다.
1번 문제보다는 직관적이고, 개인적으로 초기화를 제대로 하지 않아 시간이 많이 걸렸다. 그리디와 누적합을 적절히 이용해서 풀면 어렵지 않은 문제였다.
2번 문제가 너무 분별력 없이 쉬워서 대회에세도 은상을 가르는 것이 3번 섭테라고 생각했지만, 3번 문제의 1, 2번 섭태를 수학을 사용해서 O(1)로 풀려고 하였으나, 먹히지 않았다.
223점으로 마무리하여 동상이다. 이번에 3번의 1, 2번 섭태만 긁었어도 은상이였는데 아쉽다. 다음년도 2번이 조금 더 어려워졌으면 좋겠다.
]]>https://www.acmicpc.net/problem/16993
https://www.acmicpc.net/problem/10999
위 문제처럼 문제를 푸는것은 안돼요! 이것은 최대 구간합을 구하지 못해, 오히려 그냥 누적합으로 구간합을 구하는 것이 더 빨라요!
그렇다고 해서 구간합을 누적합을 사용하고 최대값 - 최소값을 하면 최소값이 최대값의 오른쪽에 있을 수 있다는 반례가 있습니다!
그렇다면 이 문제는 어떻게 해결해야 할까요? 이 문제를 해결할 수 있는 테크닉을 금광 세그라고 합니다. 이 문제를 풀려면 마치 이분탐색을 하는 것처럼 풀어야 합니다.
f(i, j) = A[i], A[i+1], …, A[j]에서 가장 큰 연속합 (1 ≤ i ≤ j ≤ N)이라 정의하겠습니다! 히스토그램에서 가장 큰 직사각형을 세그먼트 트리로 풀어보신 분들은 그 문제를 어떻게 풀었는지 다시 한번 생각해 봅시다. 문제를 두 작은 문제로 계속 변환하여 이분탐색처럼 해결했죠? 이 문제도 비슷합니다! 문제를 더 작은 문제 2개로 분활하여 문제를 푸는 것입니다!
mid = (i + j) / 2
f(i, j) = max(f(i, mid), f(mid + 1, j), (두 케이스를 합친 경우))
#include<bits/stdc++.h>
using namespace std;
#define ll long long int
ll n, m;
vector<ll> arr;
typedef struct Node {
ll leftMax, rightMax, _max, psum;
};
vector<Node> tree;
Node Merge(Node left, Node right) {
Node res;
res.leftMax = max(left.leftMax, left.psum + right.leftMax);
res.rightMax = max(right.rightMax, right.psum + left.rightMax);
res._max = max({left._max, right._max, left.rightMax + right.leftMax});
res.psum = left.psum+ right.psum;
return res;
}
void Init(ll start, ll end, ll node) {
if (start == end) {
tree[node].rightMax = arr[start];
tree[node].leftMax = arr[start];
tree[node]._max = arr[start];
tree[node].psum = arr[start];
return;
}
ll mid = (start + end) / 2;
Init(start, mid, node * 2);
Init(mid + 1, end, node * 2 + 1);
tree[node] = Merge(tree[node * 2], tree[node * 2 + 1]);
}
Node Query(ll start, ll end, ll left, ll right, ll node) {
if ((end < left) || (right < start)) {
Node res;
res.rightMax = INT_MIN;
res.leftMax = INT_MIN;
res._max = INT_MIN;
res.psum = INT_MIN;
return res;
}
if ((left <= start) && (end <= right)) {
return tree[node];
}
ll mid = (start + end) / 2;
return Merge(Query(start, mid, left, right, node * 2), Query(mid + 1, end, left, right, node * 2 + 1));
}
int main(void) {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n;
arr.resize(n);
tree.resize(n * 4);
for (ll i = 0; i < n; i++) {
cin >> arr[i];
}
Init(0, n - 1, 1);
cin >> m;
for (ll i = 0; i < m; i++) {
ll a, b;
cin >> a >> b;
Node res = Query(0, n - 1, a - 1, b - 1, 1);
cout << res._max << "\n";
}
}
만약 1번 풀이 필요하시다면 후기 보시면 됩니다.
나중에 BOJ에 나오면 소스코드 추가하겠습니다.
]]>2교시 1번 문제를 가뿐하게 풀고, 1시간 10 ~ 20분 정도가 남아 있었다. 평소에 구현 연습을 안해서 2번 문제 같은 경우 충분히 풀 수 있을 것 같은데 생각을 정리하지 못해서 그냥 말려버렸다. 또, 3번 문제 같은 경우 문제이해가 전혀 안갔다. 그래서 2교시는 100점으로 마무리했다. 잘봤으면 200점 정도는 되었을것 같은데 아쉽다.
최종 165점으로 마무리해서 본선을 통과하지 못할지도 모른다. 진짜 나는 알고리즘을 파는 것보다 수학을 연습하는게 오히려 좋을 지도 모른다. 작년 초등부에서도 1차보다 2차에서 잘본 것 같다. 워낙 내가 수학 선행을 안하고 이산수학을 싫어하다 보니 이런 것일지도 모른다는 생각이 든다. 진짜 이번에 1교시 좀만 더 집중했으면 은상 가능했을 것 같기도 하고, 현재 상태로 동상이라도 받을 수 있으면 다행일 것 같다.
중등부 2교시 1번문제 풀이 좌표평면 상에 그래프의 기울기는 어떻게 구하는지부터 생각해 보자. 이거는 간단하다.
| $(y1 - y2) / (x1 - x2)$로 구할 수 있다. 이 문제에서는 직각삼각형의 빗변이 아닌 변의 기울기가 1 혹은 -1이다. 그러므로 $xi - yi$이 가장 작은 값이랑, $xi + yi$가 가장 큰 값을 구해서 $y$값에 대입해 주고, $xi - yi$이 가장 큰 값이랑, $xi + yi$가 가장 작은 값을 구해서 $y$값에 대입해 주어, 대입해서 나온 두 $ | x1 - x2 | $ 들중에 작은 것을 고르면 된다. |
소스코드:
#include <bits/stdc++.h>
using namespace std;
int main(void) {
int n;
cin >> n;
vector<pair<int, int>> arr(n);
vector<int> r(n);
vector<int> l(n);
int _min = INT_MAX;
int _max = INT_MIN;
for (int i = 0; i < n; i++) {
cin >> arr[i].first >> arr[i].second;
r[i] = arr[i].first + arr[i].second;
l[i] = arr[i].first - arr[i].second;
_min = min(_min, arr[i].second);
_max = max(_max, arr[i].second);
}
sort(r.begin(), r.end());
int a = r[r.size() - 1];
sort(l.begin(), l.end());
int b = l[0];
int x1 = a - _min;
int x2 = b + _min;
int res = abs(x1 - x2);
a = r[0];
b = l[l.size() - 1];
x1 = a - _max;
x2 = b + _max;
res = min(res, abs(x2 - x1));
cout << res;
}
중등부 2교시 2번 배낭문제 스타일의 DP인데, DP배열을 두개로 늘려서 각각 계산하면 되는줄 알았다. 하지만, 두명을 동시에 세는 경우에서 일이 꼬이기 시작한다. 나는 map 사용하고 zral했는데 안풀렸다. 1번 문제는 10분 정도만에 풀었는데, 나머지 시간동안 이 한문제를 못풀었다. 또 이 문제는 적당한 구현 실력이 필요하였지만, 나는 구현을 매우 못한다. 항상 더럽게 코드를 짜는 경향이 있어, 못풀었을지도 모른다.
중등부 2교시 3번 문제 이해부터가 안된다. 무슨 건초의 배치를 어쩌라는 건지 모르겠고, 왜 어떤 경우에서 -1이 나오는지 전혀 이해가 안간다. 내가 문제를 재대로 안읽은 이슈가 제일 크겠지만, 내 국어 이슈가 여기까지 따라오는 것 같다.
앞으로 발전해야 할 것