https://boj.ma/7346/t

 

7346번: 유전자 함수

 

boj.ma

 

주어지는 두 유전자 문자열의 길이를 같게 적절히 공백을 추가했을 때 얻어지는 결괏값의 최대치를 알고 싶습니다.

또한 같은 위치에 두 문자열 모두에 공백이 있으면 안 됩니다.

주어지는 문자열의 길이는 100을 넘지 않습니다.

 

완전탐색부터 생각해 보면

문자열 두 개를 만들건대 문자열 각각의 인덱스를 n, m이라고 하면 첫 번째 문자열의 n번째 위치에 공백을 넣거나, 두 번째 문자열의 m번째 위치에 공백을 넣거나, 두 문자열 모두에 공백을 넣지 않거나

3가지 경우에 대해 분기를 나누어 진행하면 답을 찾을 수 있을 것 같습니다.

하지만 모든 경우에 대해 해 본다면 시간초과입니다.

 

예제의 문자열을 그대로 들고 왔습니다.

AGTGATG
GTTAG

 

 

첫 번째 문자열의 3번 인덱스까지 만들었고,  두 번째 문자열의 2번 인덱스까지 만들었을 때 그 이후에 공백을 적절히 채워 최댓값을 찾는 것은 이전에 어떤 방식으로 공백을 채웠든 항상 일정할 것입니다.

따라서 dp로 최적화가 가능합니다.

 

코드(c++)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 100000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    vector<vector<int>> arr = {
            {5, -1, -2, -1, -3},
            {-1, 5, -3, -2, -4},
            {-2, -3, 5, -2, -2},
            {-1, -2, -2, 5, -1},
            {-3, -4, -2, -1, 0}
    };
    string s = "ACGT*";
    map<char, map<char, int>> mp;

    for(int i = 0; i < 5 ;i ++){
        for (int j = 0 ; j < 5; j++){
            mp[s[i]][s[j]] = arr[i][j];
        }
    }

    int t;
    cin >> t;
    while(t--){
        int n, m;
        string s1, s2;
        cin >> n >> s1 >> m >> s2;
        if (n < m) swap(n, m), swap(s1, s2);
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, -1));
        auto recur = [&](const auto&self, int a, int b) -> int{
            if (a == n){
                int total = 0;
                for (int i = b ;i < m; i++){
                    total += mp['*'][s2[i]];
                }
                return total;
            }
            if (b == m){
                int total = 0;
                for (int i = a ;i < n; i++){
                    total += mp[s1[i]]['*'];
                }
                return total;
            }
            int&ret = dp[a][b];
            if (ret != -1) return ret;
            ret = -INF;
            // 두개를 직접 비교
            ret = max(ret, self(self, a + 1, b + 1) + mp[s1[a]][s2[b]]);
            // s1을 공백으로 채운다
            ret = max(ret, self(self, a, b + 1) + mp['*'][s2[b]]);
            // s2를 공백으로 채운다
            ret = max(ret, self(self, a + 1, b) + mp[s1[a]]['*']);
            return ret;
        };

        cout << recur(recur, 0,0 ) << endl;

    }


    return 0;
}

 

약간 귀찮은 전처리만 해주면 됩니다

 

 

 

'알고리즘 문제 풀이' 카테고리의 다른 글

백준 2001 보석 줍기  (2) 2024.08.31
백준 17943 도미노 예측  (0) 2024.07.18
백준 23889 돌 굴러가유  (0) 2024.05.16
백준 31786 최고의 친구  (0) 2024.05.09
백준 31782 저체온증  (0) 2024.05.02

https://boj.ma/10719/t 

 

10719번: Baekjoon Database Management System

 

boj.ma

 

 

문제 설명은 생략합니다

 

i번째로 읽어야 할 데이터에 대해 선택지가 여러개 있습니다

Area1에 저장된 데이터가 이번에 읽어야 할 데이터와 같다면, 아무 비용도 내지 않고 읽고 넘어가기.

다르다면, i번째 데이터를 c원 내고 저장소에 저장하고 읽고 넘어가기 혹은 저장소는 그대로 두고 i번째 데이터를 읽는 비용을 내고 넘어가기 이렇게 분기가 나누어 질것입니다.

 

완전탐색으로 모든 경우를 해보면 답을 찾을 수 있을것 같습니다.

하지만 n <= 10^5 으로 모든 i번째마다 경우에 따라 분기한다면 지수함수처럼 경우의수가 증가할 것 같습니다.

당연히 시간초과입니다.

 

모든 경우를 보는것이 오래걸리니, 안 봐도 되는 경우에 대해 생각해봤습니다.

예제를 그대로 들고와보면

10 3 5
2 2 4
1 1 1 1 1 2 2 2 3 2

 

9번째 데이터에 3이 처음 나오는데 9번째 데이터를 읽어야 한다고 생각해봅니다, 저장소에는 2가 저장되어있다고 생각해봅니다.

 

현재 9번째 데이터를 읽어야 하고, 저장된 데이터가 2일때 n번째 데이터까지 읽는 최소비용 을 알고싶은건데

9번째 이전까지 어떻게 선택했든, 저장소에 2가 있다면 3을 새로 저장하지 않고 3을 읽는 비용 4원을 내고 마지막 2를 읽고

끝내는게 최소인 것은 항상 똑같을 것입니다.

앞에서 어떻게 다르게 선택하고 9번째까지 오고 저장소에 데이터가 2가 있으면, 최소비용은 항상 일정합니다.

그렇다면 한 번 본 경우는 굳이 다시 안봐도 되겠지요. 따라서 dp로 최적화가 가능합니다.

 

i번째 데이터를 읽어야 하고 j번 데이터가 저장돼있을때 로 재귀함수를 정의합니다

recur(cur, save)

cur에는 1 ~ n번째 데이터까지 들어갈 수 있고

save에는 최대 1~m개의 데이터를 저장할 수 있습니다.

dp배열에 dp[cur][save]상태를 저장할 것이므로 N*M사이즈의 dp배열이 필요합니다.

이 dp배열을 채울것이므로 총 시간복잡도는 O(NM)이 되어 시간내에 통과 가능합니다.

 

"i번째로 읽어야 할 데이터에 대해 선택지가 여러개 있습니다

Area1에 저장된 데이터가 이번에 읽어야 할 데이터와 같다면, 아무 비용도 내지 않고 읽고 넘어가기.

다르다면, i번째 데이터를 c원 내고 저장소에 저장하고 읽고 넘어가기 혹은 저장소는 그대로 두고 i번째 데이터를 읽는 비용을 내고 넘어가기 이렇게 분기가 나누어 질것입니다."

 

이 선택지로 만들어지는 상태들에 대해 그래프로 표현한다면 DAG가 만들어지기 때문에 dp를 적용할 수 있다는것을 알 수 있습니다.

 

 

코드 (c++)

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 1000000007
#define X first
#define Y second
#define INF 100000000
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
using namespace std;
// pbds
//using namespace __gnu_pbds;
//// multi-set
//using ordered_set_equal = tree<int, null_type, less_equal<int>, rb_tree_tag, tree_order_statistics_node_update>;
//// set
//using ordered_set = tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update>;


int main() {
    fastio
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    int t;
    cin >> t;
    while(t--){
        int n, m, c;
        cin >> n >> m >> c;
        vector<int> data(m + 1), arr(n);
        for (int i = 1; i <= m; i++) cin >> data[i]; // i번 데이터를 읽는 비용
        
        for (int&a : arr) cin >> a; // 읽어야 할 데이터 순서
        
        vector<vector<int>> dp(n, vector<int>(31, -1));
        
        auto recur = [&](const auto&self, int cur, int save){
            if (cur == n) return 0;
            int&ret = dp[cur][save];
            if(~ret) return ret;
            ret = INF;
            if (save == arr[cur]){ // 저장소에 저장된거랑 이번에 읽을거랑 똑같음
                ret = min(ret, self(self, cur + 1, save)); // 비용 안내고 읽고 넘어가기
            }else{ // 저장소랑 현재 데이터랑 다름
             	// 저장소 유지하고 비용 내고 넘어가기
                ret = min(ret, self(self, cur + 1, save) + data[arr[cur]]);
                // 저장소에 현재 데이터 저장하고 저장비용 내고 넘어가기
                ret = min(ret, self(self, cur + 1, arr[cur]) + c); 
            }
            return ret;
        };
        
        cout << recur(recur, 0, 0) << endl;
    }
    return 0;
}

 

 

 

https://boj.ma/5546/t 

 

5546번: 파스타

 

boj.ma

 

N일동안 파스타를 먹을건데, 파스타의 종류는 3가지이고 3일 연속으로 같은 종류의 파스타를 먹으면 안됩니다.

그리고 특정 날짜에 반드시 먹어야 하는 파스타의 종류가 주어집니다.

 

N일까지 파스타를 먹는 모든 경우의 수를 구하는 문제입니다.

 

완전탐색부터 접근해봅니다.

 

재귀함수로 완전탐색을 할건데,

recur(cur, a, b) := 현재 cur일이고, 어제 a종류의 파스타, 그제 b종류의 파스타를 먹었을때 N일까지 도착하는 경우의 수

라고 정의 하고 완전탐색을 하면

제가 계산한건 대략 9^100 이라고 생각했는데 혹시 아니라면 댓글 부탁드립니다.

9^100번 연산을 하면 시간초과라는 것을 알 수 있습니다.

 

현재 5일차고, 어제 1번종류, 그제 2번 종류 파스타를 먹었다고 해봅시다. 그리고 우리는 10일차까지 가야합니다.

그럼 1 ~ 4일차 까지 1 2 2 1, 3 2 2 1, 2 1 2 1, ... 등과 같이 채울 수 있을텐데, 1 ~ 4일차에 어떤 선택을 했든 상관없이

"현재 5일차고, 어제 1번종류, 그제 2번 종류 파스타를 먹었다" 에서 N일차까지 가는 경우의 수는 항상 일정할 것입니다.

다음 경우에 영향을 주는 요소는 어제랑 그제 뿐이니까요.

따라서 dp를 적용할 수 있습니다.

 

완전탐색에서의 함수 정의를 그대로 사용하면 됩니다.

code(c++)

#include <bits/stdc++.h>
#define endl "\n"
#define NM 101010
#define MAX 1010100
#define BIAS 1048576
#define MOD 20170805
#define X first
#define Y second
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define FOR(i) for(int _=0;_<(i);_++)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define all(v) v.begin(), v.end()
#define fastio ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

int n, k;
int arr[111], dp[111][111][111];
const int mod = 10000;

int recur(int cur, int a, int b){ // 현재 cur일이고, 어제 a, 그제 b 먹음
    if (cur == n + 1) return 1;
    int&ret = dp[cur][a][b];
    if(~ret) return ret;
    ret = 0;
    if (arr[cur]){ // 오늘 먹어야할게 정해져있음
        if (a == b && b == arr[cur]) return 0;
        ret += recur(cur + 1, arr[cur], a);
        ret %= mod;
    }
    else{
        for (int i = 1; i <= 3; i++){
            if (a == b && b == i) continue;
            ret += recur(cur + 1, i, a);
            ret %= mod;
        }
    }
    return ret;
}

void input() {
    cin >> n >> k;
    for (int i = 0; i < k ; i++){
        int a, b;
        cin >> a >> b;
        arr[a] = b;
    }
}

void pro() {
    memset(dp, -1, sizeof dp);
    cout << recur(1, 0, 0) << endl;
}

int main() {
    fastio;
#ifndef ONLINE_JUDGE
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif
    input();
    pro();
    return 0;
}

 

 

+ Recent posts