반응형

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

 

2749번: 피보나치 수 3

첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

1. 아래의 식을 바탕으로 $F_{n}$의 값을 구할 수 있습니다.

 

$\begin{pmatrix}
1 &  1\\
1 &  0\\
\end{pmatrix}^{n} = \begin{bmatrix}
F_{n+1} & F_{n} \\
F_{n} & F_{n-1} \\
\end{bmatrix}$

 

2. 분할 정복을 활용하여 n이 2의 배수라면 $F_{n} = F_{\frac{n}{2}}\times F_{\frac{n}{2}}$ 아니라면 $F_{n-1}\times F_{1}$을 통해 구해줍니다.

 

3. 메모이제이션을 활용하여 각각의 $F_{i}$을 1,000,000으로 나눈 나머지 값을 저장합니다.

 

4. $F_{n}$의 [0, 1] 항을 출력해 줍니다.

 

[ 소스코드 ]

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
#include<iostream>
#include<vector>
#include<map>
#define M 1000000
#define ll long long
 
using namespace std;
 
ll n;
vector<vector<ll>> mat = { {1,1},{1,0} };
map<ll, vector<vector<ll>>> m;
 
vector<vector<ll>> mul(vector<vector<ll>> a, vector<vector<ll>> b)
{
    vector<vector<ll>> ret = { {0,0},{0,0} };
 
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            ret[i][j] = (((a[i][0] % M) * (b[0][j] % M)) % M + ((a[i][1] % M) * (b[1][j] % M)) % M) % M;
        }
    }
 
    return ret;
}
 
vector<vector<ll>> conquer(ll n)
{
    if (m[n].size() != 0return m[n];
    if (n == 1return mat;
    if (n == 0return { {1,0},{0,1} };
    if (n % 2 == 0) {
        return m[n] = mul(conquer(n / 2), conquer(n / 2));
    }
    else {
        return m[n] = mul(conquer(n - 1), conquer(1));
    }
}
 
int main()
{
    scanf("%lld"&n);
 
    vector<vector<ll>> ans = conquer(n);
    
    printf("%d", ans[0][1]);
}
cs
반응형
반응형

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

 

17401번: 일하는 세포

출력은 N개의 줄로 구성되며, i 번째 줄에는 N개의 정수 xi1, xi2, ..., xiN를 공백으로 구분하여 출력해야 한다. xij는 0초 때 거점 i 에서 출발하여 정확히 D초 때 거점 j에 위치하게 되는 경로의 수를 1

www.acmicpc.net

 

 

[ 문제풀이 ]

 

이 문제를 풀기 전에 다음 글을 먼저 읽고 오시는 것을 추천드립니다!

https://rudalsd.tistory.com/38

 

[ 백준 ] 12850번 - 본대 산책2 (C++)

https://www.acmicpc.net/problem/12850 12850번: 본대 산책2 가능한 경로의 수를 1,000,000,007로 나눈 나머지를 출력한다. www.acmicpc.net [ 문제풀이 ] 준영이가 정보관에서 1분 후 이동할 수 있는 건물은 전..

rudalsd.tistory.com

 

위 글의 문제 풀이 방식과 매우 비슷하지만 행렬이 매번 바뀌고, 바뀐 행렬에 따라 풀이 방법이 달라집니다.

 

1. 각각의 행렬을 모두 곱한 all 행렬과 나머지 행렬들을 곱한 remain 행렬을 만듭니다.

 

2. 분할 정복을 통해 구한 ans와 remain 행렬을 곱합니다.

 

[ 소스 코드 ]

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
#include<iostream>
#include<vector>
#include<unordered_map>
 
#define ll long long
#define M 1000000007
 
using namespace std;
 
int T, N, D;
 
vector<vector<int>> all(21vector<int>(210));    //모든 행렬을 곱한 행렬
unordered_map<intvector<vector<int>>> m;        //메모제이션
 
vector<vector<int>> matrix(vector<vector<int>> arr1, vector<vector<int>> arr2)    //행렬 곱
{
    vector<vector<int>> ret(N + 1vector<int>(N + 10));
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            ll sum = 0;
            for (int k = 1; k <= N; k++) {
                sum += (((ll)arr1[i][k]%M) * ((ll)arr2[k][j]%M))%M;
                sum %= M;
            }
            ret[i][j] = sum%M;
        }
    }
 
    return ret;
}
 
vector<vector<int>> conquer(int num)        //분할 정복
{
    if (num == 1) {
        return all;
    }
    if (!m[num].empty()) {
        return m[num];
    }
 
    if (num % 2 == 0) {
        return m[num] = matrix(conquer(num / 2), conquer(num / 2));
    }
    else {
        return m[num] = matrix(conquer(num - 1), conquer(1));
    }
}
 
int main()
{
    scanf("%d %d %d"&T, &N, &D);
 
    vector<vector<int>> remain(N + 1vector<int>(N + 10));    //나머지 행렬들의 곱
    vector<vector<int>> ans(N + 1vector<int>(N + 10));        //답
 
    for (int i = 1; i <= N; i++) {    //단위 행렬로 초기화
        all[i][i] = 1;
        ans[i][i] = 1;
    }
 
    int d = D % T;    //남은 행렬들의 수
 
    for (int i = 0; i < T; i++) {
        int m, a, b, c;
        scanf("%d"&m);
        vector<vector<int>> temp(N + 1vector<int>(N + 10));
 
        for (int j = 0; j < m; j++) {
            scanf("%d %d %d"&a, &b, &c);
            temp[a][b] = c;    //i번 째 행렬
        }
        if (i == d) {    //나머지 행렬
            remain = all;
        }
        all = matrix(all, temp);
    }
 
    if (D / T != 0) {    //모든 행렬들이 곱해진 행렬이 곱해지는 횟수
        ans = conquer(D / T);
        
    }
 
    ans = matrix(ans, remain);
    
 
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            printf("%d ", ans[i][j]);
        }
        printf("\n");
    }
}
cs
반응형

'백준' 카테고리의 다른 글

[ 백준 ] 9465번 - 스티커 (C++)  (0) 2022.08.12
[ 백준 ] 1932번 - 정수 삼각형 (C++)  (0) 2022.08.11
[ 백준 ] 14942번 - 개미 (C++)  (0) 2022.08.09
[ 백준 ] 13141번 - Ignition (C++)  (0) 2022.08.08
[ 백준 ] 7578번 - 공장 (C++)  (0) 2022.08.07
반응형

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

 

1629번: 곱셈

첫째 줄에 A, B, C가 빈 칸을 사이에 두고 순서대로 주어진다. A, B, C는 모두 2,147,483,647 이하의 자연수이다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

가장 기본적인 분할 정복 문제입니다. 제곱수의 값이 짝수이면 제곱수를 반으로 나누어 서로 곱하는 방식으로 진행하여 시간 복잡도를 O(logN)으로 줄일 수 있습니다. 시간을 더 단축하기 위해서 메모제이션을 해주어야 문제를 풀 수 있습니다.

 

[ 소스 코드 ]

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
#include<iostream>
#include<unordered_map>
 
#define ll long long
 
using namespace std;
 
ll A, B, C;
unordered_map<ll, ll> m;
 
ll conquer(ll num)
{
    if (num == 0return 1;
    if (num == 1return A % C;
    if (m[num] != 0return m[num] % C;
 
    if (num % 2 == 0) {
        m[num] = conquer(num / 2* conquer(num / 2);
        return m[num] % C;
    }
    else {
        m[num] = conquer(num - 1* A;
        return m[num] % C;
    }
}
 
int main()
{
    scanf("%lld %lld %lld"&A, &B, &C);
 
    printf("%lld", conquer(B) % C);
}
cs
반응형
반응형

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

 

11444번: 피보나치 수 6

첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

분할 정복과 도가뉴의 방정식을 이용하면 쉽게 풀 수 있는 문제입니다. 또한 시간 초과를 방지하기 위해서 메모제이션을 해주어야 하는데 n의 값이 매우 크므로 unordered_map을 활용하여 문제를 풀었습니다.

 

도가뉴의 방정식은 다음과 같습니다.

 

$a_{2n}=a_{n}(a_{n} + 2a_{n-1})$

$a_{2n-1}=a_{n}^{2}+a_{n-1}^{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
#include<iostream>
#include<unordered_map>
 
#define ll long long
 
using namespace std;
 
ll n;
unordered_map<ll, ll> m;
 
ll conquer(ll num)
{
    if (num == 1return 1;
    if (num == 0return 0;
    if (m[num] != 0return m[num];
    if (num % 2 == 0) {        //도가뉴의 항등식
        ll temp = num / 2;
        m[num] = conquer(temp)*(conquer(temp) + 2 * conquer(temp - 1));
        return m[num] %= 1000000007;
    }
    else {
        ll temp = (num + 1/ 2;
        m[num] = conquer(temp) * conquer(temp) + conquer(temp - 1* conquer(temp - 1);
        return m[num] %= 1000000007;
    }
}
 
int main()
{
    scanf("%lld"&n);
 
    printf("%lld", conquer(n));
}
cs

 

반응형
반응형

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

 

10830번: 행렬 제곱

크기가 N*N인 행렬 A가 주어진다. 이때, A의 B제곱을 구하는 프로그램을 작성하시오. 수가 매우 커질 수 있으니, A^B의 각 원소를 1,000으로 나눈 나머지를 출력한다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

일반적인 분할 정복 문제와 똑같이 풀면 됩니다!

 

제곱수 n이 짝수일 때 $A^{\frac{n}{2}}\times A^{\frac{n}{2}}$ 홀수일 때 $A^{n-1}\times A$의 방식으로 계산해나가면 O(logn)의 시간 복잡도로 풀 수 있습니다.

 

[ 소스 코드 ]

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
#include<iostream>
#include<vector>
 
#define ll long long
 
using namespace std;
 
int N;
ll B;
 
vector<vector<int>> A;        //행렬 A
vector<vector<int>> ans;    //정답 행렬
 
vector<vector<int>> gop(vector<vector<int>> a, vector<vector<int>> b)        //행렬 곱
{
    vector<vector<int>> ret;
    ret.resize(N);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int sum = 0;
            for (int k = 0; k < N; k++) {
                sum += (a[i][k] * b[k][j]) % 1000;
                sum %= 1000;
            }
            ret[i].push_back(sum);
        }
    }
 
    return ret;
}
 
vector<vector<int>> conquer(ll num)        //분할 정복
{
    if (num == 1) {
        return A;
    }
 
    if (num % 2 == 0) {
        vector<vector<int>> temp = conquer(num / 2);
        return gop(temp, temp);
    }
    else {
        return gop(conquer(num - 1), conquer(1));
    }
}
 
int main()
{
    cin >> N >> B;
 
    A.resize(N);
 
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int num;
            cin >> num;
            A[i].push_back(num);
        }
    }
 
    ans = conquer(B);
 
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            cout << ans[i][j] % 1000 << " ";
        }
        cout << endl;
    }
}
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/15824

 

15824번: 너 봄에는 캡사이신이 맛있단다

한 줄에 모든 조합의 주헌고통지수 합을 1,000,000,007로 나눈 나머지를 출력한다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

이 문제에서는 모듈러 연산이 필요하므로 다음 글을 읽고 오시면 좋습니다!

 

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

 

[ 알고리즘 ] 모듈러 연산(Modular Arithmetic)

오늘은 모듈러 연산에 대해 설명드리겠습니다. 특정 수 M을 넘는 수에 모듈러 연산을 하려면 어떻게 해야 할까요? 모듈러 연산의 특성을 활용하면 쉽게 구할 수 있습니다. [ 동작 원리 ] 정수 A와

rudalsd.tistory.com

 

어떤 한 수가 조합 내에서 최댓값이 될 때의 개수와 최솟값이 될 때의 개수를 통해 쉽게 정답을 구할 수 있습니다.

 

먼저 입력받은 수를 오름차순으로 정렬한 후 for문을 돌면서 그 수가 최댓값이 될 때의 조합의 개수는 그 수보다 작은 수의 개수가 i일 때 2^i가 됩니다. 

 

마찬가지로 그 수가 최솟값이 될 때의 조합의 개수는 그 수보다 큰 수의 개수가 j일 때 2^j가 됩니다.

 

따라서 모든 수에 대해 (2^i - 2^j) * a를 구해서 더해주면 됩니다.

 

하지만 n의 최댓값이 300,000이므로 일반적인 방법으로는 2^300,000의 수를 구하기는 쉽지 않습니다. 따라서 분할 정복을 활용하여 답을 구해줍니다.

 

[ 소스 코드 ]

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
#include<iostream>
#include<algorithm>
#include<cstring>
 
#define MOD 1000000007
 
using namespace std;
 
int N;
long long arr[300001];
long long comb[300001];
long long ans;
 
long long Comb(int n)        //분할 정복을 통한 2의 n승 구하기
{
    if (comb[n] != -1return comb[n];
    if (n % 2 == 0) {
        return comb[n] = ((Comb(n / 2) % MOD) * (Comb(n / 2) % MOD)) % MOD;
    }
    else {
        return comb[n] = ((Comb(n - 1) % MOD) * (Comb(1) % MOD)) % MOD;
    }
}
 
int main()
{
    cin >> N;
    memset(comb, -1sizeof(comb));
    comb[0= 1;
    comb[1= 2;
 
    for (int i = 0; i < N; i++) {
        scanf("%lld"&arr[i]);
    }
 
    sort(arr, arr + N);
 
    for (int i = 0; i < N; i++) {        //arr[i]가 최대가 되는 조합의 개수에서 arr[i]가 최소가 되는 조합의 개수를 빼고
        ans += ((Comb(i) - Comb(N - i - 1+ MOD) % MOD) * (arr[i] % MOD);    //거기에 arr[i]를 곱한 것을 ans에 더함
        ans %= MOD;
    }
 
    cout << ans;
}
cs
반응형
반응형

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

 

12850번: 본대 산책2

가능한 경로의 수를 1,000,000,007로 나눈 나머지를 출력한다.

www.acmicpc.net

 

 

[ 문제풀이 ]

 

준영이가 정보관에서 1분 후 이동할 수 있는 건물은 전산관과 미래관입니다. 또한 전산관에서 1분 후 이동할 수 있는 건물은 신양관, 미래관 그리고 정보관입니다. 이를 행렬로 표현하면 다음과 같습니다.

 

그렇다면 각 건물에서 2분 후 이동할 수 있는 건물은 어떻게 구할 수 있을까요? 바로 1분 후 이동할 수 있는 건물들에 대한 행렬을 서로 곱하면 됩니다.

 

따라서 n분 후 이동할 수 있는 건물을 구하는 방법은 다음과 같습니다.

 

n % 2 == 0 일 때

mat( n / 2 ) * mat( n / 2 )

 

n % 2 == 1 일 때

mat( n - 1 ) * mat( 1 )

 

재귀를 통해 D번 이동했을 때 mat( D )의 0 , 0의 값을 출력하면 정보과학관으로 돌아오는 경우의 수를 구할 수 있습니다.

 

[ 소스 코드 ]

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
#include <iostream>
#include <vector>
#include <unordered_map>
 
#define ll long long
 
using namespace std;
 
vector<vector<ll>> mat = {        //연결되어 있으면 1
    {0,1,1,0,0,0,0,0},
    {1,0,1,1,0,0,0,0},
    {1,1,0,1,1,0,0,0},
    {0,1,1,0,1,1,0,0},
    {0,0,1,1,0,1,1,0},
    {0,0,0,1,1,0,0,1},
    {0,0,0,0,1,0,0,1},
    {0,0,0,0,0,1,1,0}
};
 
int D;
ll MOD = 1000000007;
unordered_map<intvector<vector<ll>>> m;
 
vector<vector<ll>> multi(vector<vector<ll>> A, vector<vector<ll>> B)    //A, B 행렬 곱
{
    vector<vector<ll>> ret;
    for (int i = 0; i < 8; i++) {
        vector<ll> tmp;
        for (int j = 0; j < 8; j++) {
            ll sum = 0;
            for (int k = 0; k < 8; k++) {
                sum += A[i][k] * B[k][j];
                sum %= MOD;
            }
            tmp.push_back( sum );
        }
        ret.push_back( tmp );
    }
 
    return ret;
}
 
vector<vector<ll>> dfs(int num)        //행렬을 num번 곱했을 때 배열
{
    if (m.find(num) != m.end()) {
        return m[num];
    }
    if (num % 2 == 0) {
        return m[num] = multi(dfs(num / 2), dfs(num / 2));
    }
    else {
        return m[num] = multi(dfs(num - 1), dfs(1));
    }
}
 
int main()
{
    cin >> D;
 
    m[1= mat;
 
    dfs(D);
 
    cout<<m[D][0][0];
}
cs
반응형

+ Recent posts