C++/공부 정리

Pair, Backtracking

sondiaa 2021. 12. 3. 16:48

재귀호출을 구현할 때는 그림을 그려서 해주는 것이 실수를 줄일 수 있다.

주사위의 모든 경우 출력의 경우 주사위의 갯수가 정해져있으면 그냥 중복 for문 하면 되지만, 갯수가 정해지지 않았다면 재귀를 써야 한다.

 

완전탐색의 경우 가지치기를 통해서 속도를 끌어올려야 한다.

* 작은 팁: max, min 알고리즘 헤더 안의 함수로 구할 수 있음

abs() 절댓값 함수

 

Pair

Pair : 변수 2개를 편하게 묶어서 사용할 수 있다.

그렇지만 2개 이상이 되면 가독성이 떨어져서 STL의 장점을 잃음.

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    pair<int, int> t = {3, 4};
    t.first = 5;
    t.second = 17;
    cout << t.first << ' ' << t.second;
    return 0;
}

Backtracking

 

브루트포스 : 모든 경우의 수를 다 해보는 경우 

n이 정해져 있으면 for문 사용, 그렇지 않다면 재귀를 사용

 

Backtracking은 완전탐색에서 가지치기를 하는 것이다. 브루트포스안에 백트래킹이 포함되는 관계이다.

 

#include <iostream>
using namespace std;
int cnt;
void backtracking(int level, int sum)
{
    if(sum > 10) return;
    if(level == 4)
    {
        if(sum == 10)
            cnt++;
        return;
    }
    for(int i=1; i<5; i++)
    {
        backtracking(level+1, sum + i);
    }
}
int main()
{
    backtracking(0, 0);
    cout << cnt;
    return 0;
}

5개 숫자의 합이 10이 되는 모든 경우 출력

path 배열에 기록하기

#include <iostream>
using namespace std;
int cnt;
char path[5];
void backtracking(int level, int sum)
{
    if(sum > 10) return; // 가지치기
    if(level == 5)
    {
        if(sum == 10)
        {
            cout << path << '\n';
            cnt++;
        }
        return;
    }
    for(int i=1; i<5; i++)
    {
        path[level] = i + '0';
        backtracking(level+1, sum + i);
        path[level] = 0; // 없어도 되지만 디버깅을 용이하게 하기 위해서 사용
    }
}
int main()
{
    backtracking(0, 0);
    cout << cnt;
    return 0;
}

1~4까지 같은 수를 사용하지 않고 10 만들기 

가능한 경우가 없으므로 0이 나온다

#include <iostream>
using namespace std;
int cnt, used[4];
char path[5];
void backtracking(int level, int sum)
{
    if(sum > 10) return; // 가지치기
    if(level == 5)
    {
        if(sum == 10)
        {
            cout << path << '\n';
            cnt++;
        }
        return;
    }
    for(int i=1; i<5; i++)
    {
        if(used[i] == 1) continue;
        used[i] = 1;
        path[level] = i + '0';
        backtracking(level+1, sum + i);
        path[level] = 0; // 없어도 되지만 디버깅을 용이하게 하기 위해서 사용
        used[i] = 0;
    }
}
int main()
{
    backtracking(0, 0);
    cout << cnt;
    return 0;
}

최대 값 찾기

#include <iostream>
using namespace std;
int map[5][4] = {
    3, 2, 4, 1,
    0, 1, 0, 5,
    2, 0, 3, 0,
    5, 4, 0, 2,
    2, -5, 0, 3
}, maxNum = -21e8, direct[3][2] = {1, -1, 1, 0, 1, 1}, path[5];

void backtracking(int nowY, int nowX, int level, int sum)
{
    if(level == 5)
    {
        if(sum > maxNum)
            maxNum = sum;
        return;
    }
    for(int i=0; i<3; i++)
    {
        int dy = nowY + direct[i][0];
        int dx = nowX + direct[i][1];
        if(dy < 0 || dx < 0 || dy > 4 || dx > 3) continue;
        if(map[dy][dx] == 0) continue;
        path[level] = map[dy][dx];
        backtracking(dy, dx, level+1, sum+map[dy][dx]);
        path[level] = 0;
    }
}
int main()
{
    for(int i=0; i<4; i++)
    {
        path[0] = map[0][i];
        backtracking(0, i, 1, map[0][i]);
        path[0] = 0;
    }
    cout << maxNum;
    return 0;
}

또 다른 방식

#include <iostream>
using namespace std;
int map[5][4] = {
    3, 2, 4, 1,
    0, 1, 0, 5,
    2, 0, 3, 0,
    5, 4, 0, 2,
    2, -5, 0, 3
}, maxNum = -21e8, direct[3] = {-1, 0, 1};

void back(int now, int level, int sum)
{
    if(level == 5)
    {
        if(maxNum < sum)
            maxNum = sum;
        return;
    }
    for(int i=0; i<3; i++)
    {
        int dx = now + direct[i];
        int dy = level;
        if(dx < 0 || dx > 3) continue;
        if(map[dy][dx] == 0) continue;
        back(dx, dy + 1, sum+map[dy][dx]);
    }
}
int main()
{
    back(0, 0, 0);
    back(1, 0, 0);
    back(2, 0, 0);
    back(3, 0, 0);
    
    cout << maxNum;
    return 0;
}

 

DP가 아니라 백트래킹으로 돌다리 건너기 풀기

경로와 최대점수 출력

#include <iostream>
#include <cstring>
using namespace std;
int vect[20] = {1, 3, -1, 6, -3, -1, 5, 100, 3, 2, 5, 1}, direct[3] = {2, 3, 5}, maxNum = -21e8, path[11], maxPath[11];

void back(int sum, int now, int level)
{
    if(now > 11)
    {
        if(sum > maxNum)
        {
            memcpy(maxPath, path, 11 * 4);
            maxNum = sum;
        }
        return;
    }
    for(int i=0; i<3; i++)
    {
        path[level] = vect[now+direct[i]];
        back(sum + vect[now+direct[i]], now + direct[i], level + 1);
        path[level] = 0;
    }
}

int main()
{
    int k = 0;
    back(1, 0, 0);
    cout << maxNum << '\n';
    while(1)
    {
        cout << maxPath[k++] << ' ';
        if(maxPath[k] == 0) break;
    }
    return 0;
}

 

동전 최소 갯수 구하기

#include <iostream>
using namespace std;
int target = 80;
int minCnt = 21e8, num[3] = {10, 40, 50};

void back(int level, int sum)
{
    if(sum > 80 || level >= minCnt)
        return;
    if(sum == target)
    {
        if(minCnt > level)
            minCnt = level;
        return;
    }
    for(int i=0; i<3; i++)
    {
        back(level+1, sum + num[i]);
    }
}
int main()
{
    back(0, 0);
    cout << minCnt;
    return 0;
}

 

순열과 조합

순열은 used배열만 사용하면 쉽게 할 수 있다. 

 

조합은 자신 이전 것을 선택하지 않도록 하여 중복을 제거한다.

#include <iostream>
using namespace std;
char name[6] = "ATKBS", used[5], path[3];

void run(int level, int now)
{
    if(level == 3)
    {
        cout << path << '\n';
        return;
    }
    for(int i=now; i<5; i++)
    {
        if(used[i] == 1) continue;
        
        path[level] = name[i];
        run(level+1, i+1);
        path[level] = 0;
        
    }
}
int main()
{
    
    run(0, 0);
    
    return 0;
}

주사위 3개 있을 때 모든 경우의 수 출력

#include <iostream>
using namespace std;
char name[3];
void back(int level)
{
    if(level == 3)
    {
        cout << name << '\n';
        return;
    }
    for(int i=1; i<7; i++)
    {
        name[level] = i + '0';
        back(level+1);
    }
}
int main()
{
    back(0);
    return 0;
}

주사위 3개로 순열 만들기

#include <iostream>
using namespace std;
char name[3], used[6];
void back(int level)
{
    if(level == 3)
    {
        cout << name << '\n';
        return;
    }
    for(int i=1; i<7; i++)
    {
        if(used[i] == 1) continue;
        used[i] = 1;
        name[level] = i + '0';
        back(level+1);
        used[i] = 0;
    }
}
int main()
{
    back(0);
    return 0;
}

 

주사위 중복 조합

#include <iostream>
using namespace std;
char name[3], used[6];
void back(int level, int now)
{
    if(level == 3)
    {
        cout << name << '\n';
        return;
    }
    for(int i=now; i<7; i++)
    {
        
        
        name[level] = i + '0';
        back(level+1, i);
    }
}
int main()
{
    back(0, 1);
    return 0;
}

조합은 중복조합에서 used배열로 중복만 제거해주면 된다.