codeplus 백준 강사 강의 내용기반으로 정리한 내용입니다.(비공개 포스트)
나머지
나머지 연산(Modular Arithmetic)
- 컴퓨터의 정수는 저장할 수 있는 범위가 한정되어 있기 때문에 답을 M으로 나눈 나머지를 출력하라는 문제가 등장(정답을 구한 후 나누는 것이아니라 중간과정에서 모두 Modular를 취하면서 계산해야함)
- (A+B) mod M = ((A mod M) + (B mod M)) mod M
- (AXB) mod M = ((A mod M) X (B mod M)) mod M
- 나누기의 경우 성립하지 않는다: (6/3)%3 = 2
- 나누기는 페르마의 소정리에 의해 (A/B) % C = (AXB^C-2) % C 와 같다.(단, C는 소수이며, A, B는 서로소여야 함)
- 뺄셈의 경우에는 먼저 mod 연산을 한 결과가 음수가 나올 수 있기 때문에 아래와 같이한다.
- (A-B) mod M = ((A mod M) - (B mod M) + M) mod M
나머지 문제
- 첫째 줄에 (A+B)%C
- 둘째 줄에 (A%C + B%C)%C
- 셋째 줄에 (AXB) % C
- 넷째 줄에 (A%C X B%C) % C
를 출력하는 문제
첫째 줄과 둘째 줄, 셋째 줄과 넷째 줄의 결과가 같다.
#include <iostream>
using namespace std;
int main()
{
int A = 0;
int B = 0;
int C = 0;
cin >> A >> B >> C;
cout << (A + B) % C << endl;
cout << (A%C + B % C) % C << endl;
cout << (A*B) % C << endl;
cout << (A%C * B%C) % C << endl;
return 0;
}
최대공약수와 최소공배수
정답이 분수형태일 때 기약분수를 구하는 경우 사용
나머지는 수학공식 사용
Greatest Common Divisor(최대공약수)
- 최대공약수는 줄여서 GCD라고 쓴다.
- 약수 : N을 나눌 수 있는 수
- 공약수 : 두 수의 약수 중에서 공통된 약수
- 두 수 A와 B의 최대공약수 G는 A와 B의 공통된 약수 중에서 가장 큰 정수
int g = 1;
for (int i=2; i<=min(a, b); i++)
{
if (a%i == 0 && b%i == 0) {
g = i;
}
}
i가 커지니까 비교할 필요 없음
if 문에 들어가면 이전보다 항상 큰 값이 들어감
시간복잡도는 O(N)
더 빠른 방법은 유클리드 호제법
유클리드 호제법
- GCD(a, b) == GCD(b, a%b)
- a%b가 0dlaus 그 때 b가 최대 공약수 이다.
- GCD(24, 16) = GCD(16, 8) = GCD(8,0) = 8
-> 재귀함수를 사용해서 구현한 유클리드 호제밥
int gcd(int a, int b) {
if (b==0) {
return a;
} else {
gcd(b, a%b);
}
}
위의 코드 시간복잡도는 O(lgN)
재귀함수를 사용하지 않고 구현한 유클리드 호제법
int gcd(int a, int b) {
while(b!=0) {
int r = a%b;
a = b;
b = r;
}
return a;
}
위의 코드도 시간복잡도는 O(lgN)
세수의 최대공약수
- GCD(a, b, c) = GCD(GCD(a, b), c)
최소공배수
- 최소공배수는 줄여서 LCM이라고 한다.
- 최대공약수를 응용해서 구할 수 있음
- a,b 의 최대공약수를 g라고 했을 때 최소공배수 l = g(a/g)(b/g)
최대공약수와 최소공배수 문제
#include <iostream>
using namespace std;
class gcd {
public:
int operator ()(int a, int b) {
while (b != 0) {
if (b == 0) {
return a;
}
int mod = a % b;
a = b;
b = mod;
}
return a;
}
};
class lcm {
public:
int operator ()(int a, int b, int gcd) {
return gcd * (a / gcd)*(b / gcd);
}
};
int main()
{
int a = 0;
int b = 0;
gcd gcd;
lcm lcm;
cin >> a >> b;
int g = gcd(a, b);
cout << g << endl;
cout << lcm(a, b, g) << endl;
return 0;
}
최소공배수 문제
#include <iostream>
using namespace std;
class gcd {
public:
int operator ()(int a, int b) {
while (b != 0) {
if (b == 0) {
return a;
}
int mod = a % b;
a = b;
b = mod;
}
return a;
}
};
class lcm {
public:
int operator ()(int a, int b, int gcd) {
return gcd * (a / gcd)*(b / gcd);
}
};
int main()
{
int T = 0;
int A = 0;
int B = 0;
gcd gcd;
lcm lcm;
cin >> T;
while (T--) {
cin >> A >> B;
cout << lcm(A, B, gcd(A, B)) << endl;
}
return 0;
}
GCD합 문제
#include <iostream>
#include <vector>
using namespace std;
class gcd {
public:
int operator ()(int a, int b) {
while (b != 0) {
if (b == 0) {
return a;
}
int mod = a % b;
a = b;
b = mod;
}
return a;
}
};
int main()
{
int t = 0;
gcd gcd;
cin >> t;
while (t--) {
int n = 0;
cin >> n;
vector<int> arr(n,0);
for (int i = 0; i < n; ++i)
cin >> arr[i];
long long result = 0;
for (int i = 0; i < n - 1; ++i) {
for (int j = i + 1; j < n; ++j) {
result += gcd(arr[i], arr[j]);
}
}
cout << result << endl;
}
return 0;
}
-> result 값 long long 타입으로 해주어야함
소수
- 약수가 1과 자기 자신 밖에 없는 수
- N이 소수가 되려면, 2보다 크거나 같고, N-1보다 작거나 같은 자연수에 나누어 떨어지면 안된다.
두 가지 알고리즘
- 어떤 수 N이 소수인지 아닌지 판별하는 방법
- N보다 작거나 같은 모든 자연수 중에서 소수를 찾아내는 방법
어떤 수 N이 소수인지 아닌지 판별하는 방법
bool prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= n-1; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
시간복잡도가 O(N)
2보다 크거나 같고, N/2보다 작거나 같은 자연수로 나누어 떨어지면 안되는 이유
N의 약수 중에서 가장 큰 것은 N/2 보다 작거나 같기 때문
어떤 수가 소수가 아니면 N = a X b (두 자연수로 나누어 떨어져야함)
a가 가장작은 경우는 2이고 이때 b는 N/2임
따라서 n이 소수이면 2부터 N/2까지 나누어 떨어지지 않으면 됨
bool prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= n/2; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
시간복잡도는 O(N/2)인데 그냥 O(N)임
2보다 크거나 같고, 루트 N 보다 작거나 같은 자연수로 나누어 떨어지면 안되는 이유
N이 소수가 아니라면 두수의 관계 aXb로 나타낼 수 있음
N = a X b 일 때 a<=b 를 모두 만족하려면 a <= 루트 N, b >= 루트 N 여야함
a < 루트N, b < 루트N 일 때 aXb < 루트N X 루트N -> N(aXb) < N이므로 모순
a > 루트N, b > 루트N 일 때 aXb > 루트N X 루트N -> N(aXb) > N이므로 모순
따라서 a <= 루트N, b >= 루트N 여야함
루트 N 이전만 검사하면 반대쪽 검사와 같은 결과가 나옴
루트 24 는 4.xxx (1,2,3,4 | 6,8,12,24) |
bool prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i*i <= n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
시간복잡도는 O(루트N) 으로 가장 빠름
어떤 수 N이 소수인지 아닌지 판별하는데 걸리는 시간복잡도 : O(루트N)
소수찾기 문제
#include <iostream>
using namespace std;
class prime {
public:
bool operator()(int n) {
if (n < 2) {
return false;
} else {
for (int i = 2; i*i <= n; ++i) {
if (n%i == 0) {
return false;
}
}
return true;
}
}
};
int main()
{
int N;
cin >> N;
int result = 0;
int temp = 0;
prime prime;
for (int i = 0; i < N; ++i) {
cin >> temp;
if (prime(temp))
result++;
}
cout << result << endl;
}
N보다 작거나 같은 모든 자연수 중에서 소수를 찾아내는 방법
루트보다 log가 시간을 훨씬 작게 만들어줌
범위 안에 들어가는 모든 소수를 구하려면 에라토스테네스의 체를 사용함
- 지워지지 않은 수 중에서 가장 작은 수는 2이다.(1은 지워버림)
- 2는 소수이고 2의 배수를 모두 지운다.
- 3의 배수를 지운다.
- 5의 배수를 지운다.
- 7의 배수를 지운다.
- 지워지지 않은 수 중 가장 작은 수가 소수이면 해당 배수를 모두 지우는 방법
int prime[100]; // 소수 저장
int pn = 0; // 소수의 개수
bool check[101]; // 지워졌으면 true
int n = 100; // 100까지 소수
for (int i=2; i<=n; i++) {
if (check[i] == false) {
prime[pn++] = i;
for (int j = i*i; j<=n; j+=i) { // 3X2는 이미 2X3에서 지워져있음(i*i)부터~
check[j] = true;
}
}
}
1부터 N까지 모든 소수를 구하는 것이 목표이기 때문에 바깥 for문 (i)를 N까지 돌린다.
안쪽 for문 (j)는 N의 크기에 따라서, i+i 또는 i2로 바꾸는 것이 좋다(i가 백만인 경우 ii는 범위를 넘어가기 때문)
시간복잡도가 O(N*loglogN) - prime harmonic series(loglogN 이 나옴)
소수구하기 문제
#include <iostream>
#include <vector>
using namespace std;
class primecheck {
const int MAX = 1000000;
vector<bool> check;
public:
primecheck() {
check.assign(MAX + 1, false);
check[0] = check[1] = true;
for (int i = 2; i*i <= MAX; i++) {
if (check[i] == false) {
for (int j = i + i; j <= MAX; j += i) {
check[j] = true;
}
}
}
}
void solve(int m, int n) {
for (int i = m; i <= n; ++i) {
if (check[i] == false) {
cout << i << '\n'; // endl timeout...
}
}
}
};
int main()
{
int M = 0;
int N = 0;
primecheck checker;
cin >> M >> N;
checker.solve(M, N);
return 0;
}
-> 많은 양을 출력해야할 때 endl쓰면 timeout -> \n으로 변경해서 사용하기!
골드바흐의 추측
- 2보다 큰 모든 짝수는 두 소수의 합으로 표현 가능하다.
- 5보다 큰 모든 홀수는 세 소수의 합으로 표현 가능하다.
- 아직 증명되지 않은 문제이지만 10^18이하에서는 참
골드바흐의 추측 문제
- 백만 이하의 짝수에 대해서 골드 바흐의 추측을 검증하는 문제
- 먼저 소수를 다 구하고 검증하면 됨
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MAX = 1000000;
int prime[MAX];
int pn;
bool check[MAX + 1];
class primecheck {
public:
primecheck() {
check[0] = check[1] = true;
for (int i = 2; i <= MAX; i++) {
if (check[i] == false) {
prime[pn++] = i;
for (int j = i + i; j <= MAX; j += i) {
check[j] = true;
}
}
}
}
void solve(int n) {
if (n < 4) {
printf("Goldbach's conjecture is wrong.\n");
return;
}
for (int i = 1; i < pn; i++) {
if (check[n - prime[i]] == false) {
printf("%d = %d + %d\n", n, prime[i], n-prime[i]);
return;
}
}
printf("Goldbach's conjecture is wrong.\n");
}
};
int main()
{
int n = 0;
primecheck checker;
while (1) {
scanf("%d", &n);
if (n == 0)
break;
checker.solve(n);
}
return 0;
}
-> cin, cout 너무 느림… printf, scanf 로 변경해야함