C++ 코딩테스트 강좌, DNA 비밀번호

1. 문제 정의

얼마 전 한 생물학자가 DNA 시퀀스를 통해 특정 비밀번호를 만들어야 하는 상황에 처했습니다. 이 DNA 시퀀스는 A, C, G, T의 4종류의 뉴클레오타이드로 구성됩니다. 이 비밀번호는 주어진 DNA 시퀀스 내에서 선택된 A, C, G, T의 조합으로 만들어집니다.

문제 설명

주어진 문자열 S에서 길이가 K인 모든 부분 문자열 중에서 각 뉴클레오타이드의 빈도가 최소인 부분 문자열을 찾고, 그 중에서 비밀번호의 개수를 알아내는 프로그램을 작성하시오. 만약 모든 빈도가 같다면 그 빈도가 최소인 부분 문자열 중 가장 작은 사전 순서의 문자열을 선택합니다. 문자열의 길이는 주어진 문자열보다 작거나 같아야 합니다.

입력

  • 첫 번째 줄에 DNA 문자열 S의 길이 N (1 ≤ N ≤ 100,000)이 주어진다.
  • 두 번째 줄에 정수 K (1 ≤ K ≤ N)가 주어진다.
  • 세 번째 줄에 S가 주어진다.

출력

유효한 비밀번호의 개수와 그 중 가장 작은 사전 순서의 비밀번호를 출력하시오.

2. 알고리즘 분석

이 문제를 해결하기 위해 우리는 슬라이딩 윈도우(Sliding Window) 기법을 사용할 것입니다. 이 기법은 특정 길이 K의 부분 문자열을 이동하면서 각 문자(A, C, G, T)의 빈도를 카운트하여 문제를 해결할 수 있게 해줍니다.

절차

  1. K 길이의 첫 부분 문자열을 선택하여 문자의 빈도를 카운트한다.
  2. 이후 슬라이딩 윈도우를 사용하여 문자열을 이동하면서 새로운 문자가 추가될 때와 빠질 때 빈도를 조정한다.
  3. 각 K 길이의 문자열에서 뉴클레오타이드의 빈도를 검사하고, 가장 작은 빈도를 찾는다.
  4. 모든 문자열을 검사한 후 유효한 비밀번호의 개수와 가장 작은 사전순의 비밀번호를 출력한다.

3. C++ 코딩 예제

아래는 이 문제를 해결하기 위한 C++ 코드 예제입니다:

        #include <iostream>
        #include <string>
        #include <unordered_map>
        #include <vector>
        #include <algorithm>

        using namespace std;

        int main() {
            int N, K;
            string S;
            cin >> N >> K;
            cin >> S;

            unordered_map freq;
            for (int i = 0; i < K; i++) {
                freq[S[i]]++;
            }

            // 초기 결과 설정
            int min_freq = INT_MAX;
            vector passwords;

            // 슬라이딩 윈도우 하여 모든 K 길이 부분 문자열 검사
            for (int i = 0; i <= N - K; i++) {
                if (i > 0) {
                    freq[S[i - 1]]--;
                    freq[S[i + K - 1]]++;
                }

                // 가장 작은 빈도 찾기
                int cur_min = INT_MAX;
                for (const auto &entry : freq) {
                    cur_min = min(cur_min, entry.second);
                }

                // 유효한 비밀번호 추가
                if (cur_min <= (K / 4)) { // 모든 뉴클레오타이드의 빈도가 K/4 이하
                    passwords.push_back(S.substr(i, K));
                }
            }

            // 가장 작은 사전 순의 비밀번호 찾기
            sort(passwords.begin(), passwords.end());
            string smallest_password = (passwords.empty() ? "" : passwords[0]);
            
            // 결과 출력
            cout << passwords.size() << " " << smallest_password << endl;

            return 0;
        }
    

4. 코드 해설

위의 코드는 다음과 같이 작동합니다:

  • 우선 DNA 문자열과 그 길이, 부분 문자열의 길이 K를 입력받습니다.
  • 첫 K개의 문자의 빈도를 카운트하기 위해 unordered_map을 사용합니다.
  • 슬라이딩 윈도우 기법을 사용하여 각 K 길이의 부분 문자열을 이동하면서 빈도를 수정합니다.
  • 각 부분 문자열에 대해 빈도의 최소값을 계산하고, 조건에 맞는 경우에만 유효한 비밀번호 리스트에 추가합니다.
  • 마지막에 유효한 비밀번호 리스트를 정렬하여 가장 작은 사전 순의 비밀번호를 찾습니다.

5. 테스트 케이스

이제 본 문제를 해결하기 위한 몇 가지 테스트 케이스를 살펴보겠습니다:

테스트 케이스 1

        입력:
        8 4
        ACGTTGCA

        출력:
        1 ACGT
    

테스트 케이스 2

        입력:
        12 5
        ACGTTACGATC

        출력:
        3 ACCTA ACTAC TCGAT
    

테스트 케이스 3

        입력:
        6 3
        ACGTAC

        출력:
        2 ACG ACT
    

6. 결론

이번 강좌에서는 DNA 비밀번호 문제를 통해 슬라이딩 윈도우 기법을 활용한 알고리즘을 배웠습니다. 이 기법은 문자열 문제를 해결하는 데 매우 유용하게 사용될 수 있으며, 다양한 변형 문제에 적용할 수 있습니다. 이를 통해 코딩 테스트에서도 채용될 수 있는 좋은 기초 지식이 될 것입니다.

C++ 코딩테스트 강좌, DFS와 BFS 프로그램

본 글에서는 DFS(Depth First Search)와 BFS(Breadth First Search) 알고리즘을 이용한 C++ 코딩 테스트 문제를 해결하는 방법에 대해 설명하겠습니다. 각 알고리즘의 개념 및 특징, 구체적인 코드 예제와 이를 바탕으로 한 문제 풀이 과정을 단계별로 설명하도록 하겠습니다.

1. DFS(Depth First Search) 알고리즘

DFS는 그래프를 탐색하는 알고리즘 중 하나로, 깊이 우선 탐색이라고도 불립니다. 그래프의 한 정점에서 시작해 인접한 정점으로 계속해서 깊이 들어가면서 탐색을 진행합니다. 더 이상 깊이 들어갈 수 없게 되면, 마지막에 방문한 정점으로 돌아가서 다른 인접 정점을 탐색합니다.

1.1 DFS의 특징

  • 스택을 이용한 구현 또는 재귀를 사용하여 쉽게 구현할 수 있습니다.
  • 모든 정점을 한 번씩 방문하므로, 시간 복잡도는 O(V + E)입니다. 여기서 V는 정점의 수, E는 간선의 수입니다.
  • 실행 중 방문한 정점의 정보를 저장해야 하므로, 추가적인 메모리가 필요합니다.

1.2 DFS의 구현

                
#include <iostream>
#include <vector>
#include <stack>

using namespace std;

void DFS(int start, vector &visited, const vector> &adj) {
    stack s;
    s.push(start);

    while (!s.empty()) {
        int node = s.top();
        s.pop();

        if (!visited[node]) {
            visited[node] = true;
            cout << node << ' ';

            for (int i = adj[node].size() - 1; i >= 0; --i) {
                if (!visited[adj[node][i]]) {
                    s.push(adj[node][i]);
                }
            }
        }
    }
}
                
            

2. BFS(Breadth First Search) 알고리즘

BFS는 그래프를 탐색하는 알고리즘 중 하나로, 너비 우선 탐색이라고도 불립니다. 그래프의 한 정점에서 시작해 인접한 모든 정점을 먼저 방문한 후, 그 다음 깊이로 들어가며 탐색을 진행합니다.

2.1 BFS의 특징

  • 큐를 이용하여 구현합니다.
  • 모든 정점을 한 번씩 방문하므로, 시간 복잡도는 O(V + E)입니다.
  • 최단 경로 탐색에 적합한 방법입니다.

2.2 BFS의 구현

                
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

void BFS(int start, vector &visited, const vector> &adj) {
    queue q;
    visited[start] = true;
    q.push(start);

    while (!q.empty()) {
        int node = q.front();
        q.pop();
        cout << node << ' ';

        for (int i = 0; i < adj[node].size(); ++i) {
            if (!visited[adj[node][i]]) {
                visited[adj[node][i]] = true;
                q.push(adj[node][i]);
            }
        }
    }
}
                
            

3. 문제: 그래프 탐색 (DFS 및 BFS 응용)

문제 설명: 주어진 비연결 그래프에서 연결 요소의 개수를 구하는 프로그램을 작성하시오. 그래프의 정점과 간선이 주어질 때, DFS 및 BFS 알고리즘을 사용하여 연결된 정점의 집합을 탐색하여 연결 요소의 개수를 계산합니다.

3.1 입력 형식

첫째 줄에 정점의 수 N (1 <= N <= 1000)과 간선의 수 M (1 <= M <= 100,000)이 주어진다. 다음 M개의 줄에는 간선 정보가 주어진다.

3.2 출력 형식

연결 요소의 개수를 출력한다.

3.3 예제

                
Input:
5 3
1 2
2 3
4 5

Output:
2
                
            

3.4 풀이 과정

이 문제는 DFS와 BFS 알고리즘을 함께 활용하여 연결 요소의 개수를 구할 수 있습니다.

  1. 그래프를 인접 리스트 형태로 표현합니다.
  2. 결방문 정점을 기록할 visited 배열을 초기화합니다.
  3. 정점을 하나씩 순회하며, 방문하지 않은 정점이 발견될 때마다 DFS 또는 BFS를 이용하여 그 정점이 포함된 모든 정점을 방문합니다.
  4. 연결 요소가 발견될 때마다 카운트를 증가시키고, 최종적으로 카운트를 출력합니다.

3.5 C++ 코드 예제

                
#include <iostream>
#include <vector>
#include <stack>
#include <queue>

using namespace std;

void DFS(int start, vector &visited, const vector> &adj) {
    stack s;
    s.push(start);

    while (!s.empty()) {
        int node = s.top();
        s.pop();

        if (!visited[node]) {
            visited[node] = true;

            for (int i = 0; i < adj[node].size(); ++i) {
                if (!visited[adj[node][i]]) {
                    s.push(adj[node][i]);
                }
            }
        }
    }
}

int main() {
    int N, M;
    cin >> N >> M;

    vector> adj(N + 1);
    vector visited(N + 1, false);

    for (int i = 0; i < M; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u); // 무향 그래프이므로 양 방향으로 추가합니다.
    }

    int componentCount = 0;

    for (int i = 1; i <= N; i++) {
        if (!visited[i]) {
            DFS(i, visited, adj);
            componentCount++;
        }
    }

    cout << componentCount << endl;
    
    return 0;
}
                
            

4. 마무리

이번 글에서는 DFS와 BFS 알고리즘을 활용하여 그래프 탐색을 하는 방법과 관련된 문제를 해결해 보았습니다. 각 알고리즘의 특징과 구현 방법, 그리고 문제 해결 과정을 자세히 살펴보았습니다. DFS와 BFS는 그래프 관련 문제에서 매우 중요한 알고리즘이므로, 반복해서 연습하는 것이 중요합니다. 앞으로도 다양한 문제를 해결하며 알고리즘에 대한 이해를 깊이 있게 발전시켜 나가시길 바랍니다.

C++ 코딩테스트 강좌, DDR을 해보자

문제 설명

DDR은 Dance Dance Revolution의 약자로, 플레이어는 화면에 나타나는 화살표에 맞춰서 발을 움직이며 춤을 춥니다.
이 자료구조 및 알고리즘 문제에서는 DDR에서 발생할 수 있는 특정한 상황을 시뮬레이션하고,
입력으로 주어진 화살표 패턴을 기반으로 적절한 움직임을 계산하는 프로그램을 작성해야 합니다.

문제: 주어진 문자열은 화살표 패턴을 나타냅니다. 배열의 각 원소는 화살표의 방향을 나타내며,
‘U’는 위, ‘D’는 아래, ‘L’은 왼쪽, ‘R’은 오른쪽을 의미합니다.
플레이어는 적어도 K개의 화살표를 처리해야 하며, 중복 화살표는 무시해야 합니다.
플레이어가 움직인 방향의 개수를 반환하는 프로그램을 작성하세요.

입력: 문자열과 정수 K (1 ≤ K ≤ 1000)

출력: 플레이어가 움직인 방향의 개수

문제 분석

주어진 문제를 잘 이해하기 위해서는 각 화살표가 어떤 방향으로 가리키는지를 명확히 알고 있어야 합니다.
우리는 중복된 화살표를 고려하지 않아야 하며, 따라서 문자열을 처리하면서 중복된 요소를 원래의 순서대로
고려할 수 있는 데이터 구조가 필요합니다.
화살표의 개수는 유한하며 대문자 ‘U’, ‘D’, ‘L’, ‘R’로만 이루어져 있기 때문에 이 문제는 간단한 문자열 처리로 해결할 수 있습니다.

접근 방법

문제에 접근하기 위해 다음의 단계를 고려할 수 있습니다:

  1. 주어진 문자열을 순회하여 각 화살표를 확인합니다.
  2. 각 화살표를 Set에 추가하여 중복된 화살표를 자동으로 제거합니다.
  3. Set의 크기를 확인하여 플레이어가 실제로 움직인 방향의 개수를 계산합니다.

이 접근 방법은 시간 복잡도가 O(n)이며, n은 주어진 문자열의 길이입니다.
Set을 사용하면 중복을 처리하는 것이 용이하고, 최종적으로 Set의 크기를 얻는 것은 O(1)입니다.

C++ 코드 구현


#include 
#include 
#include 

int processArrows(const std::string &arrows, int K) {
    std::unordered_set uniqueArrows;
  
    // 각 화살표를 순회하면서 Set에 추가
    for (char arrow : arrows) {
        uniqueArrows.insert(arrow);
    }
  
    // K보다 적은 경우에도 상관 없이 Set의 사이즈를 리턴
    return uniqueArrows.size();
}

int main() {
    std::string arrows;
    int K;

    std::cout << "화살표 패턴을 입력하세요: ";
    std::getline(std::cin, arrows);
    std::cout << "K 값을 입력하세요: ";
    std::cin >> K;

    int result = processArrows(arrows, K);
    std::cout << "플레이어가 움직인 방향의 개수: " << result << std::endl;

    return 0;
}
            

코드 설명

위의 C++ 코드는 문제 해결을 위한 간단한 시뮬레이션을 수행합니다.
processArrows 함수는 String과 K를 인자로 받아 화살표를 처리하는 역할을 합니다.
여기서는 unordered_set를 사용하여 중복을 자동으로 제거하고 있습니다.

먼저, 주어진 화살표 문자열을 한 글자씩 순회하면서 각 화살표를 Set에 추가합니다.
Set은 중복된 요소를 허용하지 않기 때문에, 최종적으로 Set의 크기를 통해 플레이어가 실제로
움직인 방향의 개수를 파악할 수 있습니다.

main 함수에서는 사용자로부터 화살표 패턴과 K 값을 입력받고,
그 값을 processArrows 함수에 넘겨 결과를 최종 출력합니다.

결론

본 포스팅에서는 C++를 활용하여 DDR 게임의 간단한 알고리즘 문제를 해결하는 과정을 설명하였습니다.
문제를 단계별로 분석하고 접근 방법을 논의한 후, 최종적으로
효과적인 코드 구현을 통해 문제를 해결할 수 있었습니다.
이러한 접근 방식은 코딩 테스트에서 다루어야 할 여러 알고리즘 문제를 해결하는 데 매우 유용합니다.

C++ 코딩테스트 강좌, Ax + By = C

본 강좌에서는 C++ 언어를 이용하여 Ax + By = C 형태의 방정식을 푸는 방법에 대해 알아보겠습니다.
이 문제는 코딩테스트에서 자주 접할 수 있는 내용으로, 수학적 이해와 알고리즘 설계를 필요로 합니다.

문제 정의

주어진 정수 A, B, C가 있을 때, 방정식 Ax + By = C를 만족하는 정수 x와 y의 모든 조합을 구하는 문제입니다.
주의할 점은 x와 y가 정수여야 한다는 것입니다. 예를 들어 A=2, B=3, C=6일 때,
(x, y) = (0, 2), (3, 0), (1, 1)와 같은 정수 쌍이 있습니다.

문제 해결을 위한 기초 수학 개념

이 문제를 해결하기 위해서는 디오판틴 방정식(Diophantine Equation) 개념을 알아야 합니다.
디오판틴 방정식은 정수 계수를 가진 다항 방정식에서 정수 해를 찾는 문제입니다.
Ax + By = C 형태의 방정식이 일관된 해를 가지려면, gcd(A, B)C의 약수여야 합니다.

알고리즘 설계

1. 먼저 A, B, C의 GCD를 계산합니다.
2. GCD가 C의 약수인지 확인합니다.
3. GCD가 C의 약수이면, x와 y의 가능한 조합을 찾기 시작합니다.
4. 정수 해를 찾기 위해 x의 값은 0부터 시작하여 B로 나눈 나머지를 검토합니다.
5. 각 x에 대해 y를 계산하여 정수 해를 찾습니다.

C++ 코드 구현

아래는 위 알고리즘을 C++로 구현한 예시 코드입니다:

# include <iostream>
# include <vector>
# include <numeric> // for std::gcd
using namespace std;

// Ax + By = C 방정식을 만족하는 (x, y)의 모든 조합을 구하는 함수
void findIntegerSolutions(int A, int B, int C) {
    int gcd_ab = gcd(A, B);

    // GCD가 C의 약수인지 확인
    if (C % gcd_ab != 0) {
        cout << "해가 존재하지 않습니다." << endl;
        return;
    }

    // A, B, C를 GCD로 나누어 정규화
    A /= gcd_ab;
    B /= gcd_ab;
    C /= gcd_ab;

    // x의 가능한 범위를 설정
    for (int x = -100; x <= 100; ++x) {
        // y를 계산
        if ((C - A * x) % B == 0) {
            int y = (C - A * x) / B;
            cout << "(x, y) = (" << x << ", " << y << ")" << endl;
        }
    }
}

int main() {
    int A, B, C;
    cout << "A, B, C 값을 입력하세요: ";
    cin >> A >> B >> C;

    findIntegerSolutions(A, B, C);
    return 0;
}

코드 설명

– 입력으로 A, B, C를 받고 findIntegerSolutions 함수를 호출합니다.
– A, B의 GCD를 계산하여 return하고, 이를 통해 C가 GCD의 약수인지 검사합니다.
– GCD로 모든 수를 나눈 후, x의 값을 -100에서 100까지 변화시키며 y를 계산합니다.
– 방정식이 성립하는 경우 x, y 쌍을 출력합니다.

결과 보기

이제 코드 실행 결과를 확인해보겠습니다. 예를 들어 A=2, B=3, C=6인 경우 다음과 같은 결과를 얻을 수 있습니다:

A, B, C 값을 입력하세요: 2 3 6
(x, y) = (0, 2)
(x, y) = (3, 0)
(x, y) = (1, 1)

결론

이번 강좌에서는 Ax + By = C 형태의 방정식을 C++로 해결하는 방법을 살펴보았습니다.
이 문제를 통해 디오판틴 방정식의 기초 이론과, 이를 해결하기 위한 알고리즘 설계를 익히게 되었습니다.
이러한 문제는 코딩테스트에서 자주 출제되므로, 충분히 연습하셔서 자신만의 풀이 방법을 찾는 것이 중요합니다.

C++ 코딩테스트 강좌, ATM 인출 시간 계산하기

최근 수년간 IT 업계에서는 알고리즘 및 문제 해결 능력이 중요한 평가 요소로 자리잡고 있습니다. 이러한 기조의 연장선으로, 많은 기업들이 코딩테스트를 통해 지원자의 능력을 평가하고 있습니다.
이번 글에서는 C++을 활용하여 ‘ATM 인출 시간 계산하기’ 문제를 통해 기본적인 알고리즘 문제 해결 능력을 향상시키고자 합니다.

문제 설명

ATM에서는 각 사용자의 인출 요청이 들어올 때마다 요청이 처리되는 데 소요되는 시간이 다릅니다.
예를 들어, 한 사용자가 1분 후에 인출을 요청하면 그 시간은 모두 처리되고, 그 사용자는 1분 후에 인출할 수 있습니다.
만약 여러 사용자가 동시에 인출을 요청할 경우, 요청은 FIFO(선입선출)로 처리되므로, 먼저 요청된 사용자에게 먼저 처리가 이루어집니다.

사용자의 요청이 들어오는 순서와 각 요청의 소요 시간이 주어졌을 때, 모든 사용자가 인출을 완료하기 위해서 필요한 총 시간을 계산하여 출력하는 프로그램을 작성하시오.

입력 형식

  • 첫 번째 줄에는 정수 N(1 ≤ N ≤ 1000)이 주어지며, 이는 인출 요청의 수를 나타냅니다.
  • 두 번째 줄에는 인출 요청에 대한 소요 시간이 공백으로 구분되어 N개의 정수 형태로 주어집니다. (1 ≤ 각 요청 소요 시간 ≤ 100)

출력 형식

  • 모든 사용자가 인출을 완료하는 데 걸리는 총 시간을 출력합니다.

예시 입력

5
3 1 4 3 2

예시 출력

32

문제 해결 과정

문제의 핵심은 각 사용자가 인출을 완료하는 데 필요한 시간을 모두 합산하는 것입니다.
이 과정에서 우리는 요청이 수행되는 순서를 고려해야 합니다. 요청이 FIFO 방식으로 처리되므로, 먼저 요청된 사용자가 먼저 통과합니다.
우리는 이 문제를 해결하기 위해 다음과 같은 단계를 따릅니다.

1단계: 입력값 읽기

먼저 사용자로부터 요청의 수와 요청 시간들을 읽어야 합니다. C++에서는 표준 입력을 통해 데이터를 읽을 수 있습니다.


#include <iostream>
#include <vector>

using namespace std;

int main() {
    int N;
    cin >> N;

    vector<int> times(N);
    for (int i = 0; i < N; i++) {
        cin >> times[i];
    }

    return 0;
}

2단계: 총 시간 계산하기

각 사용자의 요청 시간은 요청이 처리되는 동안 그 앞에 있는 요청들 때문에 대기하는 시간이 발생합니다.
따라서 각 사용자가 요청을 완료하기 위해 소요되는 시간은 그들의 요청 소요 시간과 이전 사용자의 요청 소요 시간을 합산해야 합니다.
이를 통해 두 번째 사용자부터는 첫 번째 사용자의 소요 시간만큼의 대기 시간이 발생하게 됩니다.


#include <iostream>
#include <vector>

using namespace std;

int main() {
    int N;
    cin >> N;

    vector<int> times(N);
    for (int i = 0; i < N; i++) {
        cin >> times[i];
    }

    int totalTime = 0;
    int currentTime = 0;

    for (int i = 0; i < N; i++) {
        currentTime += times[i]; // 현재까지의 시간에 요청 소요 시간을 더함
        totalTime += currentTime; // 총 시간에 현재까지의 시간을 더함
    }

    cout << totalTime << endl; // 최종 총 시간 출력

    return 0;
}

3단계: 코드 설명

위의 코드에서 첫 번째 부분은 표준 입력으로부터 데이터를 읽기 위한 부분입니다.
vector<int> times(N);는 요청 소요 시간을 저장하기 위한 동적 배열을 생성합니다.
두 번째로, currentTime이라는 변수를 사용하여 현재까지의 시간이 축적됩니다.
사용자의 요청이 들어올 때마다 currentTime에 해당 요청의 소요 시간을 더하고, 이를 총 시간 totalTime에 축적하는 방식입니다.

전체 코드


#include <iostream>
#include <vector>

using namespace std;

int main() {
    int N;
    cin >> N; // 요청 수 입력받기

    vector<int> times(N); // 요청 소요 시간을 담을 벡터 선언
    for (int i = 0; i < N; i++) {
        cin >> times[i]; // 각 요청의 소요 시간 입력받기
    }

    int totalTime = 0; // 모든 요청 소요 시간 누적
    int currentTime = 0; // 현재 처리 시간 누적

    for (int i = 0; i < N; i++) {
        currentTime += times[i]; // 요청 소요 시간을 더함
        totalTime += currentTime; // 현재까지의 총 시간에 추가
    }

    cout << totalTime << endl; // 최종 총시간 출력

    return 0;
}

결론

이와 같은 문제를 해결함으로써, 알고리즘의 이해도와 C++ 프로그래밍 언어에 대한 이해도를 높일 수 있습니다.
각종 코딩테스트나 알고리즘 대회에서 유용하게 사용할 수 있는 기초적인 문제 해결 능력을 기르는 데 도움이 될 것입니다.
다양한 문제를 통해 실력을 쌓고 면접 준비를 하는 것이 중요하며, 이번 강좌를 통해 이러한 기본기를 다질 수 있었으면 합니다.