정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


 \(n\)의 범위가 \(1 \leq n \leq 123,456\)이므로, 에라토스테네스의 체로 해결하는게 가장 빠른 시간안에 해결할 수 있을 것으로 보인다. 에라토스테네스의 체를 사용해보자.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 123457 * 2;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[MAX_N];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));

    isPrime[1] = false;
    for (int i = 2; i < MAX_N; ++i) {
        if (isPrime[i]) {
            for (ll j = i * 1LL * i; j < (ll) MAX_N; j += i * 1LL) {
                isPrime[j] = false;
            }
        }
    }

    while (true) {
        int n, cnt = 0;
        cin >> n;
        if (n == 0) break;
		
        for (int i = n + 1; i <= 2 * n; i++) {
            if (isPrime[i]) {
                cnt++;
            }
        }
        cout << cnt << '\n';
    }

    return 0;
}

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


 

 입력의 범위가 \( 1 \leq M \leq N \leq 1,000,000\) 이므로 숫자 하나하나를 naive한 방법이나 naive하게 \(\root N\)까지 보는 방법으로 소수를 판별해서 시간 안에 통과하기는 쉽지 않아 보인다. 따라서 에라토스테네스의 체를 쓰자.

 

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 10e+6 + 1;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[MAX_N];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));

    int M, N;
    cin >> M >> N;

    isPrime[1] = false;
    for (int i = 2; i <= N; ++i) {
        if (isPrime[i]) {
            for (ll j = i * 1LL * i; j <= (ll) N; j += i * 1LL) {
                isPrime[j] = false;
            }
        }
    }

    for (int cur = M; cur <= N; ++cur) {
        if (isPrime[cur]) {
            cout << cur << '\n';
        }
    }

    return 0;
}

한가지 재미있는 점은 isPrime 배열을 매번 1,000,000개에 대해 초기화 해주면 아래와 같이 64ms이 걸리고,

위 코드와 같이 N의 범위에 맞춰서 초기화 해주면 16ms가 걸린다.

 

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. 기본

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int num;
    int divisor = 2;

    cin >> num;
    while (num != 1) {
        // 나누어 떨어지지 않으면
        if (num % divisor != 0) {
            divisor++;
        }
        else {
            cout << divisor << '\n';
            num /= divisor;
        }
    }
    return 0;
}

 

2. 개선 ver.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int num;
    int divisor = 2;

    cin >> num;
    while (num != 1) {
        if (num % divisor != 0) {
            // divisor가 짝수면 + 1, 홀수면 + 2
            divisor = divisor % 2 == 0 ? divisor + 1 : divisor + 2;
        }
        else {
            cout << divisor << '\n';
            num /= divisor;
        }
    }
    return 0;
}

 

3. \(\sqrt n\)까지 보기

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll num;
    ll divisor = 2;

    cin >> num;

    for (ll div = 2LL; div * div <= num; div = div % 2 == 0 ? div + 1LL : div + 2LL) {
        while (num % div == 0) {
            cout << div << '\n';
            num /= div;
        }
    }

    if (num > 1) {
        cout << num << '\n';
    }
    return 0;
}

 

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. naive

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int M, N;
    cin >> M >> N;

    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (cur == 1) continue;

        bool isPrime = true;
        for (int div = 2; div < cur; ++div) {
            if (cur % div == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
	
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;
	

    return 0;
}

 

2. naive(개선)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int M, N;
    cin >> M >> N;

    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (cur == 1) continue;

        bool isPrime = true;
        for (int div = 2; div <= (int)sqrt(cur); ++div) {
            if (cur % div == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
	
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;
	

    return 0;
}

 

3. 에라토스테네스의 체

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;
const int MAX_N = 10e+4 + 1;
const int MOD = 1e+9;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

bool isPrime[10001] = { 0, };
int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
	
    memset(isPrime, 1, sizeof(isPrime));
    isPrime[1] = false;
    for (int i = 2; i < 10001; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j < 10001; j += i) {
                isPrime[j] = false;
            }
        }
    }
    
    int M, N;
    cin >> M >> N;
    int sum = 0, min_prime = 10001;
    for (int cur = M; cur <= N; ++cur) {
        if (isPrime[cur]) {
            sum += cur;
            min_prime = min(min_prime, cur);
        }
    }
    if (sum == 0) cout << -1;
    else cout << sum << '\n' << min_prime;

    return 0;
}

정답 코드를 바로 보기보다는 아래 글을 먼저 읽고 푸시길 바랍니다.

https://pushback.tistory.com/8

 

소수 문제 해결의 기본

 소수 문제에서 사용가능한 기법은 세가지 정도이다.  첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다. // naive하게 소수판별 bool isPrime = true; int num = 101; for(int i = 2; i <..

pushback.tistory.com


1. naive

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (num == 1) continue;

        bool isPrime = true;
        for (int i = 2; i < num; ++i) { // num - 1까지 다 돌기
            if (num % i == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) ++ans;
    }
    cout << ans;

    return 0;
}

 

2. naive(개선)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (num == 1) continue;

        bool isPrime = true;
        for (int i = 2; i <= (int) sqrt(num); ++i) {
            if (num % i == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) ++ans;
    }
    cout << ans;

    return 0;
}

 

3. 에라토스테네스의 체

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
  
ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }
bool isPrime[1001] = { 0, };
int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    memset(isPrime, 1, sizeof(isPrime));
    isPrime[1] = false;
    for (int i = 2; i < 1001; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j < 1001; j += i) {
                isPrime[j] = false;
            }
        }
    }
    int n, num;
    cin >> n;
    int ans = 0;
    while (n--) {
        cin >> num;
        if (isPrime[num]) ans++;
    }
    cout << ans;

    return 0;
}

 

소수 문제에서 사용가능한 기법은 세가지 정도이다.

 

첫번째로 naive하게 2부터 n - 1까지 모든 수로 나누어버리는 방법이다.

// naive하게 소수판별
bool isPrime = true;
int num = 101;
for(int i = 2; i < num; ++i){
    if(num % i == 0){
    	isPrime = false;
        break;
    }
}
if(isPrime) printf("%d is prime.\n", num);
else printf("%d is not prime.\n", num);

 첫번째 방법을 좀 더 개선해보자. 수학적으로 어떤 한 수가 소수인지 판별하기 위해 그 수의 \(\sqrt n \)까지만 나누어주면 된다. 

// naive하게 소수판별(개선)
bool isPrime = true;
int num = 101;
for(int i = 2; i <= (int)sqrt(num); ++i){
    if(num % i == 0){
    	isPrime = false;
        break;
    }
}
if(isPrime) printf("%d is prime.\n", num);
else printf("%d is not prime.\n", num);

첫번째 방법도 소수를 판별하는 것에 나쁘지 않아 보인다.

그러나 어떤 구간 안에 있는 수 중에서 소수를 찾는 문제가 나오고, 그 구간 안에 너무 많은 숫자가 있거나 구간 안에 숫자들이 너무 크다면 이 방법은 가망이 없다.

왜냐하면 구간 안에 숫자 하나하나를 판별하는 건 시간 낭비가 심하기 때문이다.

그렇다면 한번에 연산으로 소수 판별이 가능한 방법이 있을까 ?

 

 

 

답은 "있다"이다. 바로 소수를 찾는 두번째 방법으로 소개할 '에라토스테네스의 체'라는 방법이 있다.

방법은 간단하다. 2부터 모든 소수들의 배수들을 배제하면 된다. 다시 말하면, 어떤 숫자 \(x\)가 배제되어있지 않다면 \(x\)는 소수일 것이다. 이게 어떻게 가능한지 보자.

 

여기 2부터 21까지 숫자가 있다. 

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

2부터 하나씩 숫자를 확인해보자. 2는 아직 배제된 적이 없다. 따라서 소수이기 때문에 2의 배수들을 모조리 배제해버리자. (배수들은 취소선이 긁히고, 소수는 파란색이 된다.)

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

2에 대한 처리를 끝냈다. 이제 다음 숫자 3을 보자. 3 역시 아직 배제되지 않았다. 3을 소수로 취급하고 3의 배수들은 모조리 배제하자.

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

3에 대한 처리를 끝냈다. 이어서 4를 봤더니 4는 이미 배제되어있다.

배제되었다는 뜻은 이 숫자가 어떤 숫자의 배수라는 것이기 때문에 소수일 수가 없다. 따라서 무시하고 지나간다.

다음으로 5는 배제되지 않았기 때문에, 소수이다. 5의 배수들을 처리해주자.

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 

위 표의 상황이 5까지 처리를 끝냈을 때의 상황이다. 계속 같은 처리를 진행해준다면 결과는

2 3 4 5 6 7 8 9 10 11
12 13 14 15 16 17 18 19 20 21

 이렇게 된다. 이 방법을 코딩만 해주면 우리는 에라토스테네스의 체를 구현할 수 있다.

// 에라토스테네스의 체
// 2부터 10000 사이에 있는 소수 확인
bool isPrime[10001]; // true면 소수 false면 소수가 아니다.

// 처음엔 모두 소수로 가정하기 위해
// 2~10000을 모두 true로 초기화, 1번 or 2번 중 취향에 맞게 사용
// 1번 memset 사용 (memset은 boolean 배열만 1로 초기화 가능)
memset(isPrime, 1, sizeof(isPrime));
// 2번 for문
for(int i = 2; i < 10001; ++i) isPrime[i] = true;

// standard
for(int current = 2; current < 10001; ++current){
    // 이 숫자가 소수라면.
    if(isPrime[current]){
    	// next는 current의 배수이기 때문에 loop를 돌 때마다 +1 아니라 +current를 해주어야한다.
    	for(int next = current * 2; next < 10001; next += current){
            isPrime[next] = false; // 방금 찾은 소수의 배수들은 모두 소수가 아니다.
        }
    }
}

// Faster
for(int current = 2; current < 10001; ++current){
    if(isPrime[current]){
    	// next * 2, next * 3 , ... next * (current - 1)은 이미 이전에 처리되었기 때문에
        // 좀 더 빠른 속도를 원한다면 current의 제곱부터 true로 바꿔주어도 된다.
    	for(int next = current * current; next < 10001; next += current){
    	    isPrime[next] = false; 
        }
    }
}

isPrime의 size만 조절해주면 코더가 원하는 범위 안에 있는 소수들을 판별할 수 있다.

한가지 주의 사항은 4만 이상의 범위에서 faster 버전을 사용할 때 current * current가 int 범위를 초과하는 overflow가 날 수 있으니, 그 때는 long long 타입을 사용하자.

 

알고리즘이 나온 기념으로 시간복잡도를 얘기해보면 2~x까지 범위 안에 소수를 찾는데 걸리는 시간은 isPrime[]를 채우는 시간과 같다.

그럼 이중 for문을 사용해서 시간복잡도가 \(O(n^2)\) 아닌가요? 라고 물을 수 있는데 수학적으로 증명했을 땐 \(O(n log log n)\)이고, 평상시에 나는 그냥 isPrime[]을 한번 도는 시간 \(O(n)\)으로 생각하고 문제를 푼다.

 

 

마지막으로 소인수 분해 방법에 대해 알아보자.

소인수 분해 그거 대충 그냥 에라토스테네스의 체를 이용해서 찾은 소수들로 쭉 나누면 안되나요 ? 라고 물으면 가능하다라고 답할 수 있다. 다시 물어보자.

그럼 언제든 에라토스테네스의 체를 이용하면 소인수분해 문제를 풀 수 있네요? 라고 물으면 불가능하다라는 답변이 나온다.

 

 왜일까?

 여기 987,654,321를 소인수 분해하는 문제가 있다.

 에라토스테네스의 체를 배웠으니 콧노래를 흥얼거리며 나는 bool isPrime[987654322]; 을 선언한다.

 선언해보고 뭔가 이상함을 느낀다. 곧이어 이게 메모리를 버틸 수 있나? 라는 생각이 든다. 당연히 못버틴다. 

 혹시 모르니 백준에 제출해봤지만, 풀릴 리가 없다. 

 

 어떻게 할까 ?

 방법은 이렇다. 꼭 소수로 나눌 필요가 없다.

 

 어떤 수 x가 있으면 2로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 3으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 4으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 5으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 6으로 나누어지지 않을 때까지 나누자.

 ...

 나누어지는 수가 1이면 break.

 

 아니 소인수 분해인데 소수로 안나누면 어떡합니까 ? ㅋㅋㅋㅋ 어이없네 

 어이없는 건 나다. 과정을 다시 생각해보자. 

 

 어떤 수 x가 있으면 2로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 3으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 4으로 나누어지지 않을 때까지 나누자.

 ( 2로 이미 나누어지지 않을 때까지 나누었는데, 4로 나눠질까 ? 그럴 리가 없다. )

 나누어지지 않으면, 5으로 나누어지지 않을 때까지 나누자.

 나누어지지 않으면, 6으로 나누어지지 않을 때까지 나누자.

( 2랑 3으로 이미 나누어지지 않을 때까지 나누었는데, 6으로 나눠질까 ? 그럴 리가 없다. )

 ... 

 나누어지는 수가 1이면  break.

 

앞에서 먼저 등장한 소수들로 이미 다 나눴기 때문에, 뒤에서 나오는 합성수들로 백날 나눠봤자 아무 일도 안일어난다. 이게 핵심이다.

 

아니 그러면 소인수 분해할 수(n)가 소수면 n번 돌아야하는거잖아요 ㅡㅡ 

어쩔 수 없다. 메모리가 못 버틴다. 이럴 땐 시간을 버리자.

// 987654321 소인수 분해
int num = 987654321;
int divisor = 2;
while (num != 1) {
	// 나누어 떨어지지 않으면
    if (num % divisor != 0) {
    	divisor++;
    }
    else{
    	cout << divisor << '\n';
    	num /= divisor;
    }
}

코드를 적고 나니 개선점이 하나 떠올랐다.

2로 소인수 분해를 하고 나면 divisor가 짝수가 될 필요가 있을까 ? 없다. 2가 유일한 짝수인 소수이다.

그렇다면 3부터는 divisor에 1을 더하는 게 아니라 2를 더하여 홀수일 때만 확인해줘도 될까 ? 문제 없다. 개선하자.

// 987654321 소인수 분해
int num = 987654321;
int divisor = 2;
while (num != 1) {
    if (num % divisor != 0) {
    	// divisor가 짝수면 +1, 홀수면 +2
    	divisor = divisor % 2 == 0 ? divisor + 1 : divisor + 2;
    }
    else{
    	cout << divisor << '\n';
    	num /= divisor;
    }
}

개선이 끝났다. 라고 생각할 때쯤 한가지 생각이 더 든다.

위에서 소수 판별할 때 \(2\) ~ \(\sqrt n\)까지만 봐주면 된다는 점을 이용할 수 있을까 ? 있다.

 

어떤 정수 \(n\)가 있다고 가정해보자. 

이 수를 \(2\)~\(\sqrt n\) 사이에 있는 정수로 나누어봤더니 그 어느 수와도 나누어 떨어지지 않았다.

이 때, \(\sqrt n\) 보다 큰 정수로 나누었을 때  \(n\)이 나누어 떨어질 확률은? 0이다.

 

왜? Why? 생각해보자.

만약 \(2\)~\(\sqrt n\) 사이에 어떤 정수 \(x\)로 \(n\)나누었더니 몫이 \(q\)이고 나머지가 0이다.

이 때 q의 범위는 ? 당연히 \(\sqrt n\) 이상이다. \(2\)~\(\sqrt n\)사이에 정수가 \(n\)을 나누어 떨어지게 할 수 있으면 그 몫은 자명하게도 \(\sqrt n\)보다 크다.

 

위 사실을 토대로 \(2\)~\(\sqrt n\) 사이에 어떤 정수로도 \(n\)을 나누어 떨어지게 할 수 없는데, \(\sqrt n\) ~ \(n\)사이에 있는 정수 중 \(n\)을 나누어 떨어지게 할 수 있는 수가 존재할까 ? 존재하지 않는다.

 

 따라서 소인수 분해 코드를 다시 한번 개선하자.

// 987654321 소인수 분해
long long num = 987654321;

for(long long div = 2LL; div * div <= num; div = div % 2 == 0 ? div + 1LL : div + 2LL){
    while(num % div == 0){
    	cout << div << '\n';
        num /= div;
    }
}

// for문이 끝나고 나온 num은 1이거나 소수이다.
if(num > 1){
    cout << num << '\n';
}

 

지금까지 9단계 1~6번을 풀기 위한 초석이 될 방법들을 소개했다.

9단계 소수 문제들 뿐만 아니라 대부분의 소수 문제는 위 틀을 벗어나지 않기 때문에(최소 공배수, 최대 공약수 제외) 잘 습득해서 소수 문제를 풀 때마다 이 글이 생각나면 매우 기분이 좋을 것 같다.

 

오타나 이해가 안되는 점은 댓글로 부탁드린다.

A. Shortest Path with Obstacle

https://codeforces.com/contest/1547/problem/A

 

Problem - A - Codeforces

 

codeforces.com

 A, B, F의 위치가 주어졌을 때 A->B로 가는 최단 거리를 구하는 문제이다.

 F가 거슬리는 경우를 생각해보자.

 A, B, F가 한 줄 위에 같이 존재하면서 F가 A, B사이에 있는 경우에만 A->B로 가는 경로에서 F를 우회하기 위해 2번의 움직임이 더 필요하다.

 그 외에 경우는  A->B 최단거리인 \(|Ax - Bx| + |Ay - By|\)가 답이 된다.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int INF = 2147483647;
const int MAX_N = 1e+5 + 1;
const int MOD = 1e5;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

void solve() {
	int Ax, Ay, Bx, By, Fx, Fy;
	
	cin >> Ax >> Ay >> Bx >> By >> Fx >> Fy;

	if (
    		((Ax == Bx && Bx == Fx) && ((Ay <= Fy && Fy <= By) || (By <= Fy && Fy <= Ay)))
		|| 
        	((Ay == By && By == Fy) && ((Ax <= Fx && Fx <= Bx) || (Bx <= Fx && Fx <= Ax)))
            ) {
		cout << abs(Ax - Bx) + abs(Ay - By) + 2 << '\n';
	}
	else {
		cout << abs(Ax - Bx) + abs(Ay - By) << '\n';
	}

	return;
}

int main() {
	ios::ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int T;
	cin >> T;
	while (T--) {
		solve();	
	}
	return 0;
}

B. Alphabetical Strings

https://codeforces.com/contest/1547/problem/B

 

Problem - B - Codeforces

 

codeforces.com

 a만 들어있는 string을 str이라고 하자. alphabetical이란, b부터 z까지 알파벳의 순서대로 str의 앞 or 뒤에 삽입했을 때 나오는 string을 일컫는다. 입력받은 string이 alphabetical인지 판별하는 게 목표인 문제이다.

 

 입력받은 string안에 들어있는 알파벳들의 위치를 기억하자. 

 이 위치를 기반으로 어떤 알파벳을 삽입하는 상황일 때 이전 알파벳의 위치를 보고 앞으로 삽입할지, 뒤로 삽입할지 판단할 수 있다. 따라서 a만 들어있는 string을 만들고 b부터 순서대로 삽입할 때 이전 알파벳의 위치가 삽입해야되는 알파벳의 위치보다 앞에 있으면 뒤에 삽입, 그렇지 않으면 앞에 삽입해준다.

 마지막으로 위 과정을 거쳐 나온 string이 입력받은 string과 같은지 확인해주면 된다.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int INF = 2147483647;
const int MAX_N = 1e+5 + 1;
const int MOD = 1e5;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int alp[26] = { 0, };
void solve() {
	memset(alp, -1, sizeof(alp)); //-1로 초기화
	string str;
	cin >> str;

	for (int i = 0; i < str.size(); ++i) {
		if (alp[str[i] - 'a'] == -1) { // 알파벳이 처음 등장
			alp[str[i] - 'a'] = i;
		}
		else if (alp[str[i] - 'a'] >= 0) { // 같은 알파벳이 두번 이상 등장
			cout << "NO\n";
			return;
		}
	}
	string ans = "a";
	for (int i = 1; i < 26; ++i) {
		if (alp[i] == -1) break; // 입력받은 string에 없는 알파벳이 나오면 break

		int prev = alp[i - 1];
		int cur = alp[i];
        
		if (prev > cur) ans = (char)('a' + i) + ans; 
		else ans += (char) ('a' + i);
	}

	if (ans == str) cout << "YES\n";
	else cout << "NO\n";

	return;
}

int main() {
	ios::ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int T;
	cin >> T;
	while (T--) {
		solve();
	}

	return 0;
}

C. Pair Programming

https://codeforces.com/contest/1547/problem/C

 

Problem - C - Codeforces

 

codeforces.com

 문제를 처음에 잘못 읽어 여러번의 WA를 받은 문제이다. 문제의 조건과 내용은 항상 정확하게 읽어야한다는 것을 다시 한번 상기시키는 문제였다.

 

 Monocarp와 Polycarp 중 한명이 매초마다 (file 마지막에 한줄을 더 추가) or (이미 있는 라인 하나를 수정)을 한다.

 현재 파일에 적혀있는 라인의 개수와, Monocarp와 Polycarp가 각각 하는 Action들의 순서와 내용이 주어졌을 때, 가능한 Action의 순서를 출력해주어야 한다.

 

 Monocarp와 Polycarp 중 한명이라도 다음에 하는 Action이 라인을 추가하는 것이라면 greedy하게 라인을 추가해주자. 만약, 둘 다 라인을 바꿔주는 Action이라면 바꿔야하는 라인이 현재 쓰여있는 라인인지 확인해주면 된다. 마지막으로 한명이 먼저 Action을 다 끝냈을 때도 고려해주자.

 

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int INF = 2147483647;
const int MAX_N = 1e+5 + 1;
const int MOD = 1e5;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int a[101] = { 0, };
int b[101] = { 0, };

void solve() {
	int k, n, m;
	cin >> k >> n >> m;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= m; ++i) cin >> b[i];

	vector<int> ans;
	int ai = 1, bi = 1;

	for (int i = 1; i <= n + m; ++i) {
		if (ai <= n && bi <= m) { // 두명 다 할 일이 남아있을 때
			if (a[ai] == 0) { // 라인 추가
				ans.push_back(0);
				k++;
				ai++;
			}
			else if (b[bi] == 0) { // 라인 추가
				ans.push_back(0);
				k++;
				bi++;
			}
			else {
				if (a[ai] <= k) { 
					ans.push_back(a[ai++]);
				}
				else if (b[bi] <= k) {
					ans.push_back(b[bi++]);
				}
				else { // 둘 다 바꿀 수 있는 라인이 없는 경우
					cout << -1 << '\n';
					return;
				}
			}
		}
		else if(ai <= n){ // b의 Action이 끝난 경우
			if (a[ai] == 0) {
				ans.push_back(0);
				k++;
				ai++;
			}
			else if (a[ai] <= k) {
				ans.push_back(a[ai++]);
			}
			else {
				cout << -1 << '\n';
				return;
			}
		}
		else if (bi <= m) { // a의 Action이 끝난 경우
			if (b[bi] == 0) {
				ans.push_back(0);
				k++;
				bi++;
			}
			else if (b[bi] <= k) {
				ans.push_back(b[bi++]);
			}
			else {
				cout << -1 << '\n';
				return;
			}
		}
		
	}

	for (int num : ans) cout << num << ' ';
	cout << '\n';
	return;
}

int main() {
	ios::ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int T;
	cin >> T;
	while (T--) {
		solve();
	}
	return 0;
}

D. Co-growing Sequence

추가 예정


E. Air Conditioners

추가 예정

'codeforce' 카테고리의 다른 글

Round #728  (0) 2021.06.26
Round #727  (0) 2021.06.25
Round #725 (Div. 3)  (0) 2021.06.14
Round #707 (Div. 2)  (0) 2021.05.25
Round #706 (Div. 2)  (0) 2021.05.24

A. Pretty Permutations

https://codeforces.com/contest/1541/problem/A

 

Problem - A - Codeforces

 

codeforces.com

 

 1부터 n까지 숫자들은 자기자신에 해당하는 인덱스에 있지 않은 상태 && 각 숫자별 움직이는 횟수 합이 가장 작게 하여 정렬시킬 수 있는 경우를 출력해야한다.

 나같은 경우 n개의 숫자를 n번 움직여서 정렬시키는 문제로 잘못 읽어서 시간을 좀 써버렸다. 아니었으면 세자리 등수 안에 들어갔을 수도..

 

 각설하고 최적의 상태는 \(2, 1, 4, 3, 6, 5, ...\) 이런 식으로 홀수 인덱스에 짝수, 짝수 인덱스에 홀수가 들어가있는 상태이다. 만약 저렇게 배열에 저장하고 \(n = 3\)일 때 출력하면 \(2, 1, 4\)가 나오기 때문에 \(n\)이 짝수인 경우 홀수인 경우 나눠서 출력해야한다.

 내가 생각한 최적은 짝수일 때 \(2, 1, 4, 3, 6, 5, ... n, n-1\), 홀수일 때 \(2, 1, 4, 3, 6, 5, ... n, n-2, n-1\)이다.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int a[101];

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    for (int i = 0; i < 101; i += 2) a[i] = i + 2;
    for (int i = 1; i < 101; i += 2) a[i] = i;
    
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        if (n % 2 == 0) {
            for (int i = 0; i < n; i++) cout << a[i] << ' ';
        }
        else {
            for (int i = 0; i < n - 3; i++) cout << a[i] << ' ';
            cout << n << ' ';
            for (int i = n - 2; i < n; i++) cout << i << ' ';
        }
        cout << '\n';
    }
    return 0;
}

B. Pleasant Pairs

https://codeforces.com/contest/1541/problem/B

 

Problem - B - Codeforces

 

codeforces.com

 완전탐색을 하는데 시간을 줄일 방법을 생각해야했다.

 \(a_i \cdot a_j = i + j\) 식을 \(a_j\)에 대하여 정리해보자.

 \(a_j = \frac{i + j}{a_i}\) 에서 이미 \(i\)와 \(a_i\)를 안다고 가정하면 \(a_j\)는 정수이기 때문에 \(j = x * a_i - i\)가 된다. 

 이제 \(x\)을 구해주기 위해 \(i < j\) 식을 이용해보면, \( i < x * a_i - i => x > 2i / a_i\)가 된다.

 따라서 \(x >= \lceil\frac{2i}{a_i}\rceil\)를 구할 수 있다.

 결국, \(j = \lceil\frac{2i}{a_i}\rceil * a_i - i\)을 시작으로 \(+a_i\)를 해주면서 문제 조건에 맞는 \(i, j\)인지 확인하면 된다.

 

 이제 시간 복잡도를 생각해보자.

 \(a_i\)마다 최대 \(\frac{n}{a_i}\)번의 연산이 일어난다는 점, \(1\leq a_i \leq 2n\)인 것과 모든 \(a_i\)는 distinct하다는 점을 이용하면, 최대

\(\frac{n}{1} + \frac{n}{2} + \frac{n}{3} + ... + \frac{n}{2n} = n(\frac{1}{1} + \frac{1}{2} + \frac{1}{3} + ... + \frac{1}{2n})\)번의 연산이 일어난다.

 마지막으로 조화 급수를 이용하면 \(n(\frac{1}{1} + \frac{1}{2} + \frac{1}{3} + ... + \frac{1}{2n}) = nlog(2n)\)로 식이 정리된다. 따라서 시간복잡도는 \(O(nlogn)\)이 된다.

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

int a[100001] = { 0, };

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
 
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];
 
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            int x = (2 * i) / a[i] + 1; // 올림
            int start = x * a[i] - i;
            
            for (int j = start; j <= n; j += a[i]) {
                if (a[j] == (i + j) / a[i]) ans++;
            }
        }
        cout << ans << '\n';
    }
    return 0;
}

C. Great Graphs

https://codeforces.com/contest/1541/problem/C

 

Problem - C - Codeforces

 

codeforces.com

 입력받은 배열 d를 우선 오름차순으로 정렬시키고 1번부터 n번 목초지가 있다고 생각해보자.

 낮은 번호에서 높은 번호로 가는 도로는 많이 지을수록 total weight cost를 낮추는 데에 도움이 안된다. 반대로 높은 번호에서 낮은 번호로 가는 도로는 많이 지을수록 total weight cost가 낮아지기 때문에 최대한 많이 지어야 한다.

 정리해보면 낮은 번호 -> 높은 번호 도로는 1->n 으로 가는 도로 하나만 짓고, 높은 번호 -> 낮은 번호 도로는 모두 다 지어야 한다. 

 

 B번 문제와 마찬가지로 이중 for문으로 완전탐색을 실시하면 \(O(n^2)\)이 되므로 TLE에 걸릴 확률이 높다. 자 그러면 문제 해결을 위해 식을 세워보자. \(n\)번에서 낮은 번호로 가는 도로들의 weight의 합을 구하는 식을 세우자.

\(n번->1번 = d_1 - d_n\)

\(n번->2번 = d_2 - d_n\)

\(...\)

\(n번->n - 1번 = d_{n-1} - d_n\)

\(Total Weight = (d_1 + d_2 + ... + d_{n-1}) - (n - 1)d_n\)

아! 찾았다. 구간 합을 이용하면 \(O(1)\)에 \(n\)번에서 낮은 번호로 가는 모든 도로의 합을 구할 수 있다.

구간 합을 통해 답을 구하는 것이기 때문에 시간 복잡도는 \(O(n)\)이 된다.

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int INF = 2147483647;

ll gcd(ll a, ll b) { for (; b; a %= b, swap(a, b)); return a; }

ll d[100001] = { 0, };
ll sum[100001] = { 0, };

int main() {
    ios::ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> d[i];
        sort(d + 1, d + n + 1);

	for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + d[i]; // 구간 합
        ll ans = d[n]; // 1 -> n번으로 가는 도로 weight

        for (int i = n; i > 1; i--) {
            ans += (sum[i - 1]) - (i - 1) * d[i];
        }

        cout << ans << '\n';
    }
    return 0;
}

 

'codeforce' 카테고리의 다른 글

Round #731 (Div. 3)  (0) 2021.07.11
Round #727  (0) 2021.06.25
Round #725 (Div. 3)  (0) 2021.06.14
Round #707 (Div. 2)  (0) 2021.05.25
Round #706 (Div. 2)  (0) 2021.05.24

+ Recent posts