반응형

KMP 알고리즘은 문자열을 탐색하는 알고리즘입니다. 보통 ctrl + f를 이용하여 원하는 문자열을 찾을 때 많이 쓰는 방식입니다.

 

단순하게 길이가 N인 문자열의 처음부터 끝까지 문자열의 길이가 M인 문자열과 비교한다면 O(NM)의 시간이 걸립니다. 하지만 KMP알고리즘을 쓴다면 O(N + M)의 시간으로 문제를 풀 수 있습니다.

 

[ 동작 원리 ]

 

먼저 찾고자 하는 문자열 P에 전처리를 해주어야 합니다.

 

문자열 ABABABAC의 전처리를 예로 들어보겠습니다.

idx가 1일 때 겹치는 문자열의 최대 개수는 0개입니다.

 

idx가 2일 때 겹치는 문자열의 최대 개수는 1개입니다.

 

idx가 3일 때 겹치는 문자열의 최대 개수는 2개입니다.

 

위 방법을 계속 반복하면 위와 같은 결과가 나옵니다.

 

하지만 idx가 7일 때 문자열이 일치하지 않으므로 cur의 값을 당겨줄 필요가 있습니다. 

 

cur = Pi [cur-1]을 대입해서 Pi[ 5 - 1 ]은 3이므로 위와 같이 당겨줄 수 있습니다.

 

위와 같은 방법으로 계속 반복면 위와 같은 결과를 얻을 수 있습니다.

 

이렇게 Pi를 완성시켰습니다.

 

이제는 이 Pi를 활용해서 KMP를 구해주면 됩니다.

 

cur이 8일 때 문자열이 다릅니다. 이때 Pi[7] = 6이므로 cur에 6을 대입 후 반복해줍니다.

 

cur이 6일 때 문자열이 다릅니다. 이 때 Pi [5] = 4이므로 cur에 4를 대입 후 반복해줍니다.

 

위와 같이 반복하다 보면 일치하는 문자열을 발견할 수 있습니다. 이때 cur은 P.size()-1의 값을 가지고, Pi [cur]은 0이므로 다음 문자열부터 다시 검색을 시작해주면 됩니다.

 

위 알고리즘을 이용하여 다음 문제를 풀 수 있습니다.

https://rudalsd.tistory.com/70

 

반응형
반응형

 

https://www.acmicpc.net/problem/1761

 

1761번: 정점들의 거리

첫째 줄에 노드의 개수 N이 입력되고 다음 N-1개의 줄에 트리 상에 연결된 두 점과 거리를 입력받는다. 그 다음 줄에 M이 주어지고, 다음 M개의 줄에 거리를 알고 싶은 노드 쌍이 한 줄에 한 쌍씩

www.acmicpc.net

 

 

[ 문제풀이 ]

 

문제 풀이 과정은 다음과 같습니다.

 

1. 각각의 노드들에 대해서 연결된 에지들을 list배열에 저장합니다.

 

2. 1번 노드를 루트 노드로 설정하고, 부모는 0, 깊이는 1로 설정합니다. 

 

3. 각 노드들의 부모, 깊이, 부모 노드까지의 거리를 각각 저장합니다.

 

4. 입력받은 노드들에 대해서 높이가 같아질 때까지 올라가고 그때까지의 거리를 저장합니다.

 

5. 노드들의 깊이가 같아졌을 때 같은 노드를 가리키고 있다면 break

 

6. 다른 노드를 가리키고 있다면 계속 올라가면서 5번을 반복합니다.

 

하지만 같은 깊이까지 한칸씩 올라가면 드는 시간이 최대 O(NM)이 걸리게 되므로 효율적이지 않습니다.

 

따라서, 깊이 정보를 $2^{i}$로 저장하게 된다면 O(MlogN)의 시간으로 단축할 수 있습니다.

 

첫 번째 코드는 같은 깊이까지 한 칸씩 올라가는 코드이고, 두 번째 코드는 $2^{i}$칸씩 올라가는 코드입니다.

 

[ 소스 코드 ]

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<iostream>        //768ms
#include<vector>
#include<queue>
 
using namespace std;
 
struct node {
    int to;
    int dist;
};
 
struct tree {
    int parent;
    int depth;
    int dist;
};
 
int N, M;
vector<node> list[40001];        //연결된 노드 저장
int visited[40001];
tree arr[40001];            //부모 노드, 깊이, 거리를 저장할 배열
 
int main()
{
    cin >> N;
 
    arr[1= { 010 };
 
    for (int i = 0; i < N - 1; i++) {        //연결된 노드 저장
        int a, b, dist;
        scanf("%d %d %d"&a, &b, &dist);
        list[a].push_back({ b,dist });
        list[b].push_back({ a,dist });
    }
 
    queue<node> q;
    q.push({ 10 });
 
    while (!q.empty())        //1번 노드의 깊이를 1로 두고 내려갈수록 깊이 +1
    {
        int to = q.front().to;
        q.pop();
 
        if (visited[to] == 1continue;
        visited[to] = 1;
 
        for (int i = 0; i < list[to].size(); i++) {
            int next = list[to][i].to;
            int dist = list[to][i].dist;
            if (visited[next] != 1) {
                arr[next] = { to, arr[to].depth + 1, dist };
                q.push({ next, 0 });
            }
        }
    }
 
    cin >> M;
 
    for (int i = 0; i < M; i++) {
        int a, b;
        int ans = 0;
        scanf("%d %d"&a, &b);
        if (arr[a].depth < arr[b].depth) {            //깊이가 다르다면 깊이 맞춰주기
            while (arr[a].depth != arr[b].depth) {
                ans += arr[b].dist;
                b = arr[b].parent;                
            }
        }
        else if (arr[a].depth > arr[b].depth) {
            while (arr[a].depth != arr[b].depth) {
                ans += arr[a].dist;
                a = arr[a].parent;
            }
        }
        //깊이가 같다면
        while (1) {        //부모가 같아질 때까지 올라가기
            if (a == b) {
                break;
            }
            ans += arr[a].dist + arr[b].dist;
            a = arr[a].parent;
            b = arr[b].parent;
        }
 
        printf("%d\n", ans);
    }
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include<iostream>            //40ms
#include<vector>
 
using namespace std;
 
struct node {
    int to;
    int dist;
};
 
int N, M;
vector<node> list[40001];        //연결된 노드 저장
int visited[40001];
int parent[40001][16];        //parent[i][j] i번 노드의 2^i번째 부모
int dist[40001][16];        //dist[i][j] i번 노드의 2^i번째 부모까지의 거리
int depth[40001];            //노드의 깊이 배열
 
void dfs(int cur, int D)    //현재 노드와 깊이
{
    if (visited[cur]) return;
    visited[cur] = 1;
    depth[cur] = D;
 
    for (int i = 0; i < list[cur].size(); i++) {
        int next = list[cur][i].to;
        int cost = list[cur][i].dist;
        if (visited[next] == 1continue;
        parent[next][0= cur;
        dist[next][0= cost;
        dfs(next, D + 1);
    }
}
 
int LCA(int start, int end)        //공통 조상 찾기
{
    if (depth[start] > depth[end]) {
        int temp = start;
        start = end;
        end = temp;
    }
 
    int ret = 0;
 
    for (int i = 15; i >= 0; i--)    //깊이를 같게 만들기
    {
        int mask = 1 << i;
 
        if (depth[end- depth[start] >= mask)
        {
            ret += dist[end][i];
            end = parent[end][i];
        }
    }
 
    if (start == end) {    //깊이가 같을 때 같은 노드에 있다면
        return ret;
    }
 
    for (int i = 15; i >= 0; i--) {        //부모가 같다면 최초의 공통 조상이거나 넘었거나
        if (parent[start][i] == parent[end][i]) continue;
 
        ret += dist[start][i] + dist[end][i];
        start = parent[start][i];
        end = parent[end][i];
    }
 
    ret += dist[start][0+ dist[end][0];    //start, end는 부모가 같으므로 한 칸 더 올라가야함
    return ret;
}
 
int main()
{
    cin >> N;
 
    for (int i = 0; i < N - 1; i++) {        //연결된 노드 저장
        int a, b, dist;
        scanf("%d %d %d"&a, &b, &dist);
        list[a].push_back({ b,dist });
        list[b].push_back({ a,dist });
    }
 
    dfs(11);
 
    for (int i = 1; i < 16; i++) {        //parent[node][i] node의 2^i번째 부모
        for (int j = 1; j <= N; j++) {
            int preParent = parent[j][i - 1];
            parent[j][i] = parent[preParent][i - 1];
 
            if (parent[j][i] == 0continue;
 
            int preDist = dist[j][i - 1];
            dist[j][i] = preDist + dist[preParent][i - 1];
        }
    }
 
    cin >> M;
 
    for (int i = 0; i < M; i++) {
        int a, b;
        scanf("%d %d"&a, &b);
        printf("%d\n", LCA(a, b));
    }
}
cs
반응형
반응형

 

https://www.acmicpc.net/problem/1708

 

1708번: 볼록 껍질

첫째 줄에 점의 개수 N(3 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 점의 x좌표와 y좌표가 빈 칸을 사이에 두고 주어진다. 주어지는 모든 점의 좌표는 다르다. x좌표와 y좌표의 범

www.acmicpc.net

 

 

 

[ 문제풀이 ]

 

이 문제를 풀기 전에 다음 글을 읽어보고 오시길 추천드립니다!

 

https://rudalsd.tistory.com/66?category=1064608

 

[ 알고리즘 ] 컨벡스 헐 알고리즘(Convex Hull Algorithm)

1. 컨벡스 헐 알고리즘(Convex Hull Algorithm)이란? 컨벡스 헐 알고리즘은 2차원 평면에 여러 개의 점이 있을 때 그 점들 중 일부를 사용하여 볼록 다각형을 만들고 그 안에 나머지 모든 점을 포함시

rudalsd.tistory.com

 

간단하게 문제 풀이 순서에 대해서 알려드리겠습니다.

 

1. y값이 가장 작은 점을 기준점으로 선택합니다.

 

2. 기준점을 기준으로 반시계 방향으로 점들을 정렬합니다.

 

3. 1번 점과 2번 점보다 다음 점이 반시계 방향에 있다면 stack에 넣고, 그렇지 않다면 2번 점을 빼고 다시 3번의 과정을 반복합니다.

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<iostream>
#include<algorithm>
#include<stack>
 
#define ll long long
 
using namespace std;
 
struct pos
{
    ll x;
    ll y;
};
 
pos arr[100001];
 
int N;
 
ll ccw(pos a, pos b, pos c)        //점이 직선의 반시계방향에 있으면 양수, 시계방향에 있으면 음수, 일직선상이면 0 return
{
    return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
 
bool cmp(pos left, pos right)    //가장 왼쪽 아래에 있는 점 찾기
{
    if (left.y != right.y) return left.y < right.y;
    return left.x < right.x;
}
 
bool cmp2(pos left, pos right)    //가장 왼쪽 아래에 있는 점 기준 반시계방향으로 정렬
{
    ll p = ccw(arr[0], left, right);
    if (p > 0) {
        return true;
    }
    else if (p < 0) {
        return false;
    }
    else {
        if (left.y != right.y) return left.y < right.y;
        return left.x < right.x;
    }
}
 
int main()
{
    cin >> N;
    stack <int> s;
    s.push(0);
    s.push(1);
 
    for (int i = 0; i < N; i++) {
        scanf("%lld %lld"&arr[i].x, &arr[i].y);
    }
 
    sort(arr, arr + N, cmp);
 
    sort(arr + 1, arr + N, cmp2);
 
    int next = 2;
 
    while (next < N)
    {
        while (s.size() >= 2) {        //stack에 점이 2개 이상 있다면
            int second = s.top();
            s.pop();
            int first = s.top();
 
            if (ccw(arr[first], arr[second], arr[next]) > 0) {    //다음 점이 직선의 반시계 방향에 있다면
                s.push(second);
                break;
            }
        }
 
        s.push(next);
        next++;
    }
 
    printf("%d", s.size());
cs
반응형
반응형

컨벡스 헐 알고리즘(Convex Hull Algorithm)을 이용해서 다음 문제를 풀 수 있습니다.

https://rudalsd.tistory.com/67

 

[ 백준 ] 1708번 - 볼록 껍질 (C++)

https://www.acmicpc.net/problem/1708 1708번: 볼록 껍질 첫째 줄에 점의 개수 N(3 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 점의 x좌표와 y좌표가 빈 칸을 사이에 두고 주어진다. 주어지는..

rudalsd.tistory.com

 

1. 컨벡스 헐 알고리즘(Convex Hull Algorithm)이란?

 

 

컨벡스 헐 알고리즘은 2차원 평면에 여러 개의 점이 있을 때 그 점들 중 일부를 사용하여 볼록 다각형을 만들고 그 안에 나머지 모든 점을 포함시키는 것을 의미합니다.

 

위의 그림을 컨벡스 헐 알고리즘을 통해 선을 그으면 다음과 같이 표시할 수 있습니다.

 

 

위를 볼록 껍질이라고 하고, 점 5개로 이루어져있습니다.

 

이를 알고리즘을 통해 구현할텐데 그 중에서도 O(NlogN)에 구현할 수 있는 그라함 스캔(Graham's Scan) 알고리즘을 알아보도록 하겠습니다.

 

2. 컨벡스 헐 알고리즘(Convex Hull Algorithm) 동작 원리?

그라함 스캔(Graham's Scan)을 사용하기 전에 두가지 전처리가 필요합니다.

 

1. 우선 가장 아래에 있는 점들 중 가장 왼쪽에 있는 점을 기준점으로 잡습니다.

 

2. 이 기준점을 바탕으로 모든 점에 대해 반시계방향에 있는 순서대로 정렬을 합니다.

 

이 과정을 마친 후 다음과 같이 진행하면 됩니다.

 

 

먼저 스택에 1번과 2번 점을 넣습니다.

 

 

스택에서 2번 점과 1번 점을 뽑아낸 후 next 점과 ccw를 하여 반시계방향에 있는지(ccw > 0)체크한 후 반시계방향에 있다면 볼록껍질이 될 수 있다는 뜻이고, 뽑아낸 2번 점을 다시 넣고 next를 넣어줍니다.

 

 

같은 방법을 통해 next를 또 스택에 넣습니다.

 

 

이번에는 first - second 직선보다 first - next 직선이 오른쪽에 있으므로 ccw 값은 0보다 작게 나옵니다. 그렇다면 second 점은 볼록 껍질의 점이 될 수 없으므로 second는 다시 스택에 넣지 않고 위 과정을 반복합니다.

 

 

ccw값이 0보다 크므로 next를 stack에 넣어줍니다.

 

이러한 과정들을 모두 거치면 다음과 같은 결과를 얻을 수 있습니다.

 

 

반응형
반응형

https://www.acmicpc.net/problem/1086

 

1086번: 박성원

첫째 줄에 정답을 기약분수 형태로 출력한다. p/q꼴로 출력하며, p는 분자, q는 분모이다. 정답이 0인 경우는 0/1로, 1인 경우는 1/1로 출력한다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

모든 조합에 대해 문제를 푼다면 O(15!)이므로 2초 안에 풀 수 없습니다. 따라서 비트 마스킹을 통해 중복으로 쓰이는 순열들을 저장해서 씀으로써 시간을 줄일 수 있습니다.

 

또한 $123456$의 나머지를 구할 때는 (123*1000) + 456 이므로 ( ( 123 % K ) * ( 1000 % K ) ) % K + 456 % K로 구해주시면 됩니다.

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include<iostream>
#include<string>
#include<cstring>
 
#define ll long long
 
using namespace std;
 
int N, K;
string arr[16];
ll dp[1 << 15][100];
int mods[15];
int digit[51];
 
ll GCD(ll a, ll b)        //최대 공약수 구하기
{
    while (a != 0) {
        ll temp = b % a;
        b = a;
        a = temp;
    }
 
    return b;
}
 
int mod(string num)        //입력된 숫자를 K로 나눈 나머지 구하기
{
    int ret = 0;
 
    for (int i = 0; i < num.size(); i++) {
        ret = (ret * 10) % K + (num[i] - '0') % K;
        ret %= K;
    }
 
    return ret;
}
 
ll Fac(int num)        //팩토리얼
{
    ll ret = 1;
 
    for (int i = 1; i <= num; i++) {
        ret *= i;
    }
 
    return ret;
}
 
ll DP(int cur, int mod)        //나머지별로 개수 저장
{
    if (cur == (1 << N) - 1) {    //모든 수를 썼는가?
        if (mod == 0return 1;
        else return 0;
    }
 
    ll& ret = dp[cur][mod];
 
    if (ret != -1return dp[cur][mod];    //사용된 적이 있는가?
        
    ret = 0;
 
    for (int i = 0; i < N; i++) {
        int mask = 1 << i;
 
        if (cur & mask) continue;        //이미 사용된 숫자면 continue
 
        ret += DP(cur | mask, ((mod * digit[arr[i].size()]) % K + mods[i]) % K);
    }
 
    return ret;
}
 
int main()
{
    memset(dp, -1sizeof(dp));
    digit[0= 1;
 
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> arr[i];
    }
 
    cin >> K;
 
    for (int i = 0; i < N; i++) {
        mods[i] = mod(arr[i]);
    }
 
    for (int i = 1; i <= 50; i++) {
        digit[i] = ((digit[i - 1] % K) * (10 % K)) % K;
    }
 
    ll numerator = DP(00);    //분자
    ll denominator = Fac(N);    //분모
    if (numerator == 0) {
        cout << "0/1";
        return 0;
    }
 
    if (numerator == denominator) {
        cout << "1/1";
        return 0;
    }
    
    ll gcd = GCD(numerator, denominator);
 
    cout << numerator / gcd << "/" << denominator / gcd;
}
cs
반응형
반응형

https://www.acmicpc.net/problem/17371

 

17371번: 이사

$(\frac{2}{3}, \frac{1}{3})$으로 이사를 가면 가장 가까운 편의시설은 (0, 1)으로 거리는 $\frac{2\sqrt{2}}{3}$이고, 가장 먼 편의시설은 (-4, 1) 혹은 (4, -3)으로 거리는 둘 다 $\frac{10\sqrt{2}}{3}$이다. 두 거리의

www.acmicpc.net

 

 

[ 문제풀이 ]

 

생각을 조금만 해보면 쉽게 풀 수 있는 문제입니다.

 

점들 중 하나를 골라 다른 점들과 거리를 비교했을 때 가장 가까운 점은 거리가 0, 가장 먼 거리가 d일 때 평균 거리는 $ \frac{d}{2}$입니다. 따라서 이들 중 가장 짧은 거리를 구해주면 됩니다.

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
#include<cmath>
 
using namespace std;
 
int N;
 
struct pos {
    int x;
    int y;
};
 
pos arr[1000];
int ans;
 
int main()
{
    cin >> N;
 
    for (int i = 0; i < N; i++) {
        cin >> arr[i].x >> arr[i].y;
    }
 
    double minDist = 9999999;
 
    for (int i = 0; i < N; i++) {
        int x = 0int y = 0;
        double maxDist = 0;
        for (int j = 0; j < N; j++) {    //i번째 점부터 j번째 점까지 거리 중 가장 먼 거리를 maxDist에 저장
            double dist = sqrt(pow(arr[i].x - arr[j].x, 2+ pow(arr[i].y - arr[j].y, 2));
            maxDist = max(maxDist, dist);
        }
        
        if (minDist > maxDist) {        //maxDist 중 가장 짧은 거리를 minDist에 저장하고 idx를 ans에 저장
            ans = i;
            minDist = maxDist;
        }
    }
 
    cout << arr[ans].x << " " << arr[ans].y;
}
cs
반응형
반응형

https://www.acmicpc.net/problem/14428

 

14428번: 수열과 쿼리 16

길이가 N인 수열 A1, A2, ..., AN이 주어진다. 이때, 다음 쿼리를 수행하는 프로그램을 작성하시오. 1 i v : Ai를 v로 바꾼다. (1 ≤ i ≤ N, 1 ≤ v ≤ 109) 2 i j : Ai, Ai+1, ..., Aj에서 크기가 가장 작은 값의

www.acmicpc.net

 

 

[ 문제풀이 ]

 

이 문제를 풀기 전에 다음 글을 읽고 오시면 좋습니다!!

https://rudalsd.tistory.com/51?category=1073064 

 

[ 자료구조 ] 세그먼트 트리 (Segment Tree)

1. 세그먼트 트리 (Segment Tree) 먼저, 세그먼트 트리가 무엇인지 알아봅시다! 세그먼트 트리는 구간 합을 저장하기 위한 트리입니다. 예를 들어 size가 5인 배열이 있다고 생각해봅시다. int arr [5] = {1

rudalsd.tistory.com

 

이 문제는 가장 작은 수의 인덱스를 출력해야 하는 문제이기 때문에 node struct를 만들어서 숫자와 인덱스를 저장합니다. 나머지는 다른 세그먼트 트리와 같은 방식으로 풀어주면 됩니다.

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include<iostream>
#include<vector>
#include<cmath>
 
using namespace std;
 
struct node {
    int num;
    int idx;
};
 
int N, M;
int arr[100001];
vector<node> segTree;
 
node makeTree(int cur, int start, int end)    //세그먼트 트리 만들기
{
    int mid = (start + end/ 2;
    if (start == end) {
        return segTree[cur] = { arr[start], start };
    }
 
    node left = makeTree(cur * 2, start, mid);    //왼쪽 자식 노드
    node right = makeTree(cur * 2 + 1, mid + 1end);    //오른쪽 자식 노드
 
    if (left.num < right.num) {
        return segTree[cur] = { left.num, left.idx };
    }
    else if (right.num < left.num) {
        return segTree[cur] = { right.num, right.idx };
    }
    else {
        if (left.idx < right.idx) {
            return segTree[cur] = { left.num, left.idx };
        }
        else {
            return segTree[cur] = { right.num, right.idx };
        }
    }
}
 
node updateTree(int cur, int start, int endint idx)    //세그먼트 트리 업데이트
{
    if (idx < start || idx > end) {        //start ~ end에 idx가 포함되어 있지 않을 때
        return segTree[cur];
    }
    if (start == end) {        //바꿀 숫자의 idx에 도착했을 때
        return segTree[cur] = { arr[start], start };
    }
 
    int mid = (start + end/ 2;
    node left = updateTree(cur * 2, start, mid, idx);    //왼쪽 자식 노드
    node right = updateTree(cur * 2 + 1, mid + 1end, idx);    //오른쪽 자식 노드
 
    if (left.num < right.num) {
        return segTree[cur] = { left.num, left.idx };
    }
    else if (right.num < left.num) {
        return segTree[cur] = { right.num, right.idx };
    }
    else {
        if (left.idx < right.idx) {
            return segTree[cur] = { left.num, left.idx };
        }
        else {
            return segTree[cur] = { right.num, right.idx };
        }
    }
}
 
node minTree(int cur, int start, int endint sidx, int eidx)    //최솟값 구하기
{
    if (end < sidx || start > eidx) {    //범위를 완전히 벗어났을 때
        return { 1111111111987654321 };
    }
    if (sidx <= start && eidx >= end) {    //범위에 완전히 포함될 때
        return segTree[cur];
    }
    //범위에 일부 포함될 때
    int mid = (start + end/ 2;
 
    node left = minTree(cur * 2, start, mid, sidx, eidx);
    node right = minTree(cur * 2 + 1, mid + 1end, sidx, eidx);
 
    if (left.num < right.num) {
        return left;
    }
    else if (right.num < left.num) {
        return right;
    }
    else {
        if (left.idx < right.idx) {
            return left;
        }
        else {
            return right;
        }
    }
}
 
int main()
{
    cin >> N;
 
    for (int i = 1; i <= N; i++) {
        scanf("%d"&arr[i]);
    }
 
    int size = (1 << (int)(ceil(log2(N) + 1)));
    segTree.resize(size);
    makeTree(11, N);
 
    cin >> M;
 
    for (int i = 0; i < M; i++) {
        int a, b, c;
        scanf("%d %d %d"&a, &b, &c);
 
        if (a == 1) {
            arr[b] = c;
            updateTree(11, N, b);
        }
        else {
            node ans = minTree(11, N, b, c);
            printf("%d\n", ans.idx);
        }
    }
}
cs
반응형
반응형

https://www.acmicpc.net/problem/13977

 

13977번: 이항 계수와 쿼리

\(M\)개의 자연수 \(N\)과 정수 \(K\)가 주어졌을 때 이항 계수 \(\binom{N}{K}\)를 1,000,000,007로 나눈 나머지를 구하는 프로그램을 작성하시오.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

이 문제를 풀기 전에 다음 글을 먼저 읽어보시는 걸 추천드립니다.

 

https://rudalsd.tistory.com/61?category=1064608 

 

[ 알고리즘 ] 페르마의 소정리(Fermat's little theorem)

페르마의 소정리(Fermat's little theorem)는 다음과 같이 정리할 수 있습니다. $a\geq 0$에 대하여 $a^{p}\equiv a($mod $p)$를 만족하고, 만약 $p$가 소수이고, $a$와 $p$가 서로소이면 $a^{p-1}\equiv 1($mod..

rudalsd.tistory.com

 

먼저 $\binom{N}{K}$는 다음과 같이 나타낼 수 있습니다.

 

$\binom{N}{K} = \frac{N!}{(N-K)!K!}$

 

그리고 페르마의 소정리를 이용하여 mod $m$에서 어떤 수를 $a$로 나눈다는 뜻은 $a$의 역원을 곱한다는 뜻과 같습니다. 따라서 $m$이 소수일 때, $a^{m-1}\equiv 1($mod $m)$이고, $a\times a^{m-2}\equiv 1($mod $m)$이므로 mod $m$에서 $a$에 대한 곱셈의 역원은 $a^{m-2}$입니다.

 

따라서, 

 

$\frac{N!}{(N-K)!K!} \equiv N!\times ((N-K)!K!)^{m-2}($mod $m)$

 

이 됩니다.

 

분모의 수로 나누는 방법 대신 $m-2$제곱한 값을 곱해주면 모듈러 연산 시 같은 결과가 나오는 것을 알 수 있습니다.

이 때 $m$의 값이 $1,000,000,007$로 매우 크므로 분할 정복을 이용한 거듭제곱을 이용하여 문제를 풀어주면 됩니다.

 

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
 
#define ll long long
#define MOD 1000000007
 
using namespace std;
 
ll fac[4000001];
 
int M, N, K;
 
ll pow(ll num, int p)        //분할 정복을 이용한 거듭제곱
{
    if (p == 0return 1;
    if (p == 1return num;
 
    if (p % 2 == 0) {
        ll temp = pow(num, p / 2);
        temp %= MOD;
        return (temp * temp) % MOD;
    }
    else {
        ll temp = pow(num, p - 1);
        temp %= MOD;
        return (num * temp) % MOD;
    }
}
 
int main()
{
    fac[0= 1;
 
    for (int i = 1; i <= 4000000; i++) {
        fac[i] = fac[i - 1* i;
        fac[i] %= MOD;
    }
 
    scanf("%d"&M);
 
    for (int i = 0; i < M; i++) {
        scanf("%d %d"&N, &K);        //페르마의 소정리
        ll ans = (fac[N] * pow((fac[N - K] * fac[K]) % MOD, MOD - 2)) % MOD;
        printf("%lld\n", ans);
    }
}
cs
반응형
반응형

https://www.acmicpc.net/problem/11689

 

11689번: GCD(n, k) = 1

자연수 n이 주어졌을 때, GCD(n, k) = 1을 만족하는 자연수 1 ≤ k ≤ n 의 개수를 구하는 프로그램을 작성하시오.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

이 문제를 풀기 전에 다음 글을 먼저 읽어보시는 걸 추천드립니다!

https://rudalsd.tistory.com/59?category=1064608 

 

[ 알고리즘 ] 오일러 피 함수(Euler's phi function)

오일러 피(파이) 함수(Euler's phi function)는 1부터 n까지의 정수 중 n과 서로소인 수의 개수를 구하는 함수이고, Ф(n)으로 표현합니다. 이때, 서로소는  1 이외에 공약수를 갖지 않는 둘 이상의 양의

rudalsd.tistory.com

 

위의 공식을 이용하여 ans에 n을 대입한 후 n에 대하여 어떠한 수 i로 나누어지면 ans = ans - ans / i를 통해서 답을 구할 수 있습니다.

 

이때 i * i <= n까지 구하는 이유는 n의 제곱근은 i보다 작거나 같기때문입니다.

 

하지만 마지막에 n이 1이 아니라면 남은 n은 소수이므로 n이 1이 아닐 때 마지막으로 ans = ans - ans / n을 해주면 답을 구할 수 있습니다.

 

[ 소스 코드 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
 
#define ll long long
 
using namespace std;
 
ll n;
ll ans;
 
int main()
{
    cin >> n;
    ans = n;
 
    for (ll i = 2; i * i <= n; i++) {
        int cnt = 0;
        bool flag = false;
        while (n % i == 0) {        //소인수분해
            n /= i;
            flag = true;
        }
 
        if (flag) {        //오일러 피
            ans = ans - ans / i;
        }
    }
 
    if (n != 1) {        //남은 n이 소수라면
        ans = ans - ans / n;
    }
 
    cout << ans;
}
cs
반응형
반응형

페르마의 소정리(Fermat's little theorem)는 다음과 같이 정리할 수 있습니다.

 

$a\geq 0$에 대하여 $a^{p}\equiv a($mod $p)$를 만족하고,

만약 $p$가 소수이고, $a$와 $p$가 서로소이면 $a^{p-1}\equiv 1($mod $p)$를 만족한다.

 

수학적 귀납법을 이용하여 위의 식을 증명해보겠습니다.

먼저, $0\leq a\leq p-1$ 범위의 정수에서만 증명해도 충분합니다. 그 이유는 $a$를 $p$로 나눈 나머지는 항상 $0$보다 크거나 같고 $p-1$보다 작거나 같기 때문입니다.

 

1. $a = 0$일 때

 

$a$가 $0$인 경우에는 $0^{p} = 0\equiv 0(mod$ $p)$

 

2. $a = k$일 때 성립한다고 가정하고 $a = k+1$일 때

 

$a=k$일 때 성립한다고 가정했으므로, $k^{p}\equiv k(mod$ $p)$를 만족합니다.

이항 정리를 사용하여 $a = k + 1$일 때도 성립함을 증명합니다.

 

$(k+1)^{p}=\sum_{i=0}^{p}\binom{p}{i}k^{i}$

 

이때 이항 계수의 정의에 의해서

 

$\binom{p}{i} = \frac{p!}{(p-i)!i!}=\frac{p(p-1)\cdot \cdot \cdot(p-i+1)}{i(i-1)\cdot \cdot \cdot 1}$

 

이고, 위 식은 $1\leq i\leq p-1$일 때 $p$의 배수이므로 p로 나누면 없어지게 됩니다. 또한, $i$가 $0$ 또는 $p$이면 1입니다. 따라서,

 

$(k+1)^{p}\equiv \binom{p}{0}k^{0}+\binom{p}{p}k^{p} = k^{p}+1\equiv k+1($mod $p)$

 

$a=k+1$일 때도 성립함을 증명했습니다. 따라서,따라서, $a\geq 0$에 대하여 $a^{p}\equiv a($mod $p)$를 만족합니다.

 

$a$와 $p$가 서로소인 경우는 다음과 같이 유도할 수 있습니다.

 

$a^{p}\equiv a($mod $p)$이므로, $a^{p} - a = a(a^{p-1}-1)$은 $p$로 나누어 떨어집니다. 이때, $a$와 $p$가 서로소이므로 $a^{p-1}-1$은 $p$로 나누어 떨어집니다.

 

따라서, $a^{p - 1}\equiv 1($mod $p)$를 만족합니다.

반응형

+ Recent posts