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

 

17780번: 새로운 게임

재현이는 주변을 살펴보던 중 체스판과 말을 이용해서 새로운 게임을 만들기로 했다. 새로운 게임은 크기가 N×N인 체스판에서 진행되고, 사용하는 말의 개수는 K개이다. 말은 원판모양이고, 하나의 말 위에 다른 말을 올릴 수 있다. 체스판의 각 칸은 흰색, 빨간색, 파란색 중 하나로 색칠되어있다. 게임은 체스판 위에 말 K개를 놓고 시작한다. 말은 1번부터 K번까지 번호가 매겨져 있고, 이동 방향도 미리 정해져 있다. 이동 방향은 위, 아래, 왼쪽, 오른쪽

www.acmicpc.net

17837번 새로운 게임 2 문제(문제 링크)와 거의 똑같은데 조금 더 쉬운 시뮬레이션 문제이다.

(문제 풀이 링크)

이 문제에서는 말이 맨 밑에 있지 않으면 이동시킬 수 없다는 것이 17837번 문제와의 차이점이다.

 

 

 

말의 정보 구조체를 만들어서 행과 열의 위치, 방향을 넣어서 벡터에 저장한다.

벡터의 i번째 위치에는 i번째 말의 위치와 방향이 저장되어 있다.

 

방향 정보는 문제에서 알려준 순서대로 오른쪽, 왼쪽, 위쪽, 아래쪽이 각각 1, 2, 3, 4번의 인덱스가 되도록 배열에 넣어놓는다. 1번부터 시작하기 위해서 0번 위치에는 그냥 0을 넣어놨다.

 

말을 이동시키기 위해서는 deque배열을 사용하였다.

deque<int> dq[13][13];

위와 같이 deque<int>를 타입으로 가지는 2차원 배열이다.

dp[x][y]에는 x행 y열 위치에 있는 말들이 순서대로 저장되어 있다.

 

 

 

이제 턴을 진행하면서 4개 이상이 쌓이는 경우가 있다면 바로 게임을 종료하고 턴 수를 리턴한다.

문제 조건에 따라 1,000보다 크거나 절대로 게임이 종료되지 않는 경우에는 -1을 출력하기 위해서 -1을 리턴한다.

각 턴에서는 1번 말부터 k번말까지 순차적으로 이동시킨다.

 

 

문제 조건에 따라 이동시키기 전에 현재 이동시킬 말이 맨 밑에 있지 않다면 이동할 수 없으므로 바로 리턴한다.

이동시킬 말이 맨 밑에 있다면 이동할 칸의 색깔에 따라서 이동하게 된다.

 

이동할 칸

  1. 범위를 벗어나거나 파란색 칸인 경우
    • 원래 좌표에서 방향을 반대로 바꾼다.
      1. 다시 한 칸을 이동할 건데 이동할 칸이 또 범위를 벗어나거나 파란색일 경우 문제 조건에 따라 방향만 바꾼 채로 더 이동하지 않는다.
      2. 그렇지 않다면 이동을 한 건데 이동할 칸이 흰색이냐 빨간색이냐에 따라 다르게 이동할 것이므로 아래 조건들(밑에 2번, 3번 조건)을 이어서 실행해주면 된다.
  2.  흰색인 경우
    • 현재 위치에 있는 말들을 순서대로 (큐의 앞부분부터) 이동할 위치로 이동시킨다.
  3. 빨간색인 경우
    • 현재 위치에 있는 말들을 반대 순서로(큐의 뒷부분부터) 이동할 위치로 이동시킨다.

 

 

 

이동을 매번 할 때마다이동한 칸에 쌓인 말이 4개 이상이 되었는지 확인하고

4개 이상이 되었다면 true를 리턴, 그렇지 않다면 false를 리턴한다.

 

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
 
int n, k;
int map[13][13];
 
//오, 왼, 위, 아래
int dx[] = { 00,0,-1,1 };
int dy[] = { 01,-1,0,0 };
 
 
deque<int> dq[13][13];
 
struct Horse {
    int x;
    int y;
    int dir;
    Horse(int a, int b, int c) {
        x = a;
        y = b;
        dir = c;
    }
};
 
//말의 정보를 저장할 벡터
vector<Horse> vt;
 
bool move(int num) {
 
    //num번호 말의 좌표와 방향
    int x = vt[num].x;
    int y = vt[num].y;
    int dir = vt[num].dir;
 
    int nx, ny, tmp;
 
    //이동할 좌표
    nx = x + dx[dir];
    ny = y + dy[dir];
 
    //말이 맨 밑에 있지 않으면 이동할 수 없다.
    if (dq[x][y].front() != num) return false;
 
 
    //이동할 좌표가 파란색이거나 범위를 벗어나는 경우
    if (nx < 1 || ny < 1 || nx > n || ny > n || map[nx][ny] == 2) {
        if (dir == 1 || dir == 3) dir++;
        else dir--;
 
        //반대로 다시 한칸 이동
        nx = x + dx[dir];
        ny = y + dy[dir];
 
        //말의 방향 정보 업데이트
        vt[num].dir = dir;
 
 
        //다시 이동한 칸이 또 파란색이거나 범위를 벗어나면 방향만 바꾼채로 리턴
        if (nx < 1 || ny < 1 || nx > n || ny > n || map[nx][ny] == 2) {
            return false;
        }
        //그렇지 않다면 아래의 남은 코드 진행
    }
 
    //이동할 칸이 흰색인 경우
    if (map[nx][ny] == 0) {
        //현재 칸에 있는 말들을 순서대로 이동할칸으로 옮겨준다.
        while (!dq[x][y].empty()) {
            tmp = dq[x][y].front();
            dq[x][y].pop_front();
 
            dq[nx][ny].push_back(tmp);
            vt[tmp].x = nx;
            vt[tmp].y = ny;
        }
 
    }
    else if (map[nx][ny] == 1) {
    //이동할 칸이 빨간색인 경우
 
        //현재 칸에 있는 말들을 뒤쪽부터 순서대로 이동할 칸으로 옮겨준다.
        while (!dq[x][y].empty()) {
            tmp = dq[x][y].back();
            dq[x][y].pop_back();
 
            dq[nx][ny].push_back(tmp);
            vt[tmp].x = nx;
            vt[tmp].y = ny;
        }
 
    }
 
 
    //말이 4개 이상 쌓이면 true를 리턴
    if (dq[nx][ny].size() >= 4return true;
    else return false;
}
 
 
 
int turnStart() {
    int turnCnt = 1;
 
 
    while (turnCnt <= 1000) {
 
        bool flag = false;
        for (int i = 1; i <= k; i++) {
            flag = move(i);
 
            //말이 4개이상 쌓여서 true값을 리턴받았다면 게임 종료(턴 수를 리턴)
            if (flag) return turnCnt;
        }
 
        turnCnt++;
    }
 
 
    //1000번이 넘으면 -1을 리턴
    return -1;
}
 
 
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie();
 
    cin >> n >> k;
 
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> map[i][j];
        }
    }
 
 
    //1번부터 사용하기 위해 0번째 위치에는 쓰레기값 넣어 놓는다.
    vt.push_back(Horse(-1-1-1));
 
 
    int x, y, d;
    for (int i = 1; i <= k; i++) {
        cin >> x >> y >> d;
 
        //말의 정보를 벡터에 넣는다.
        vt.push_back(Horse(x, y, d));
 
        //말이 있는 좌표에 말의 번호를 넣는다.
        dq[x][y].push_back(i);
    }
 
    cout << turnStart() << '\n';
 
 
    return 0;
}
Colored by Color Scripter
 

'BOJ' 카테고리의 다른 글

[BOJ] 4179. 불!  (0) 2020.02.17
[BOJ] 1938. 통나무 옮기기  (0) 2020.02.12
[BOJ] 17837. 새로운 게임 2  (0) 2020.02.03
[BOJ] 1600. 말이 되고픈 원숭이  (0) 2020.02.01
[BOJ] 2532. 반도체 설계  (0) 2020.01.29

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

 

17837번: 새로운 게임 2

재현이는 주변을 살펴보던 중 체스판과 말을 이용해서 새로운 게임을 만들기로 했다. 새로운 게임은 크기가 N×N인 체스판에서 진행되고, 사용하는 말의 개수는 K개이다. 말은 원판모양이고, 하나의 말 위에 다른 말을 올릴 수 있다. 체스판의 각 칸은 흰색, 빨간색, 파란색 중 하나로 색칠되어있다. 게임은 체스판 위에 말 K개를 놓고 시작한다. 말은 1번부터 K번까지 번호가 매겨져 있고, 이동 방향도 미리 정해져 있다. 이동 방향은 위, 아래, 왼쪽, 오른쪽

www.acmicpc.net

시뮬레이션 문제이다. 어떻게 말의 정보들을 저장하고 말을 이동시킬까 고민하다가 다음과 같이 구현해봤다.

 

 

 

말의 정보구조체를 만들어서 행과 열의 위치, 방향을 넣어서 벡터에 저장한다.

벡터의 i번째 위치에는 i번째 말의 위치와 방향이 저장되어 있다.

 

방향 정보는 문제에서 알려준 순서대로 오른쪽, 왼쪽, 위쪽, 아래쪽이 각각 1, 2, 3, 4번의 인덱스가 되도록 배열에 넣어놓는다. 1번부터 시작하기 위해서 0번 위치에는 그냥 0을 넣어놨다.

 

말을 이동시키기 위해서는 deque배열을 사용하였다.

deque<int> dq[13][13];

위와같이 deque<int>를 타입으로 가지는 2차원 배열이다.

dp[x][y]에는 x행 y열 위치에 있는 말들이 순서대로 저장되어 있다.

 

(queue가 아닌 deque를 사용한 이유는 이동할 칸이 빨간색인경우 순서를 반대로 넣어주기 위해서 사용하였다.)

(효율적인 방법인지는 잘모르겠다. 채점현황을 보니 메모리를 더 사용하는 것 같은데 다른 분들 풀이도 찾아봐야겠다...)

 

 

 

이제 턴을 진행하면서 4개 이상이 쌓이는 경우가 있다면 바로 게임을 종료하고 턴 수를 리턴한다.

문제 조건에 따라 1,000보다 크거나 절대로 게임이 종료되지 않는 경우에는 -1을 출력하기 위해서 -1을 리턴한다.

각 턴에서는 1번말부터 k번말까지 순차적으로 이동시킨다.

 

 

 

이동할 칸

  1. 범위를 벗어나거나 파란색 칸인 경우
    • 원래 좌표에서 방향을 반대로 바꾼다.
      1. 다시 한칸을 이동할건데 이동할 칸이 또 범위를 벗어나거나 파란색일 경우 문제 조건에 따라 방향만 바꾼채로 더 이동하지 않는다.
      2. 그렇지 않다면 이동을 한건데 이동할 칸이 흰색이냐 빨간색이냐에 따라 다르게 이동할 것이므로 아래 조건들(밑에 2번, 3번 조건)을 이어서 실행해주면 된다.
  2.  흰색인 경우
    • 현재 이동할 말의 번호가 num이라고 했을때
    • 현재 이동할 말의 위에 있는 말들을 함께 이동시켜주기위해서 deque에서 num번 말이 나오는 이후의 말들은 모두 같이 이동시켜주면된다.
    • deque에 들어있는 말들의 개수만큼만 반복문을 진행하면서
      1. num번이 나오지 않았다면 다시 원래 위치 deque에 넣어준다.
      2. num번이 나왔거나 이전에 num번이 나왔다면 이동할 위치의 deque에 넣어준다. (이동할 때는 해당 말의 위치정보(벡터에 넣어놓은 값)을 업데이트 해주어야한다)
  3. 빨간색인 경우
    • 현재 이동할 말의 번호가 num이라고 했을때
    • 현재 이동할 말의 위에 있는 말들을 순서를 반대로 이동시켜주기 위해서 deque의 뒷부분부터 num번까지의 말들을 순차적으로 이동시켜준다.
    • deque에 들어있는 말들의 개수만큼만 반복문을 진행하면서 num이 나올때까지 이동할 위치의 deque에 넣어주고 num번이 나온다면 num번말도 이동시켜주고 반복문을 종료한다.

 

 

 

이동을 매번 할때마다 이동한 칸에 쌓인 말이 4개 이상이 되었는지 확인하고

4개 이상이 되었다면 true를 리턴, 그렇지 않다면 false를 리턴한다.

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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
 
int n, k;
int map[13][13];
 
//오, 왼, 위, 아래
int dx[] = { 0,0,0,-1,1 };
int dy[] = { 0,1,-1,0,0 };
 
 
deque<int> dq[13][13];
 
struct Horse {
    int x;
    int y;
    int dir;
    Horse(int a, int b, int c) {
        x = a;
        y = b;
        dir = c;
    }
};
 
 
//말의 정보를 저장할 벡터
vector<Horse> vt;
 
 
bool move(int num) {
 
    //num번호 말의 좌표와 방향
    int x = vt[num].x;
    int y = vt[num].y;
    int dir = vt[num].dir;
 
    int nx, ny, tmp;
    
    //이동할 좌표
    nx = x + dx[dir];
    ny = y + dy[dir];
 
 
    //이동할 좌표가 파란색이거나 범위를 벗어나는 경우
    if (nx < 1 || ny < 1 || nx > n || ny > n || map[nx][ny] == 2) {
        //방향 전환
        if (dir == 1 || dir == 3) dir++;
        else dir--;
 
        //반대로 다시 한칸 이동
        nx = x + dx[dir];
        ny = y + dy[dir];
        
 
        //말의 방향정보 업데이트
        vt[num].dir = dir;
 
 
        //다시 이동한 칸이 또 파란색이거나 범위를 벗어나면 방향만 바꾸고 리턴
        if (nx < 1 || ny < 1 || nx > n || ny > n || map[nx][ny] == 2) {
            return false;
        }
 
        //그렇지 않다면 아래남은 코드 진행
    }
    
 
    //이동할 칸이 흰색인 경우
    if (map[nx][ny] == 0) {
        bool flag = false;
 
        int size = dq[x][y].size();
        for (int i = 0; i < size; i++) {
            tmp = dq[x][y].front();
            dq[x][y].pop_front();
 
            if (tmp == num || flag) {
                //num번 말이랑 num위에 있는 말들만 이동한다.
                flag = true//ture로 바꿔주면 앞으로 뒤에 있는 말들은 같이 이동한다.
 
 
                //이동시켜준다.
                dq[nx][ny].push_back(tmp);
                
                //좌표정보 업데이트
                vt[tmp].x = nx;
                vt[tmp].y = ny;
            }
            else {
                //num번 말 아래있는 말은 다시 원래좌표에 넣어준다.
                dq[x][y].push_back(tmp);
            }
        }
 
    }
    else if (map[nx][ny] == 1) {
    //이동할 칸이 빨간색인 경우
 
        int size = dq[x][y].size();
        for (int i = 0; i < size; i++) {
            //반대 순서로 넣어줄 것이므로 뒤에것을 빼준다.
            tmp = dq[x][y].back();
            dq[x][y].pop_back();
 
 
            //이동시켜준다.
            dq[nx][ny].push_back(tmp);
 
            //좌표정보 업데이트
            vt[tmp].x = nx;
            vt[tmp].y = ny;
 
            //num번말 위에있는말들과 num번말까지만 이동시키므로 break
            if (tmp == num) break;
        }
    }
 
 
    //말이 4개 이상 쌓이면 true를 리턴
    if (dq[nx][ny].size() >= 4return true;
    else return false;
}
 
 
 
int turnStart() {
    int turnCnt = 1;
 
 
    while (turnCnt <= 1000) {
        
        bool flag = false;
 
        for (int i = 1; i <= k; i++) {
            flag = move(i);
 
            //말이 4개이상 쌓여서 true값을 리턴받았다면 게임 종료(턴 수를 리턴)
            if (flag) return turnCnt;
        }
        
        turnCnt++;
    }
 
 
    //1000번이 넘으면 -1을 리턴
    return -1;
}
 
 
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie();
 
 
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cin >> map[i][j];
        }
    }
 
 
    //1번부터 사용하기 위해 0번째 위치에는 쓰레기값 넣어 놓는다.
    vt.push_back(Horse(-1-1-1));
 
 
    int x, y, d;
    for (int i = 1; i <= k; i++) {
        cin >> x >> y >> d;
 
        //말의 정보를 벡터에 넣는다.
        vt.push_back(Horse(x, y, d));
 
        //말이 있는 좌표에 말의 번호를 넣는다.
        dq[x][y].push_back(i);
    }
 
 
    cout <<  turnStart() << '\n';
 
 
    return 0;
}
Colored by Color Scripter
 

'BOJ' 카테고리의 다른 글

[BOJ] 1938. 통나무 옮기기  (0) 2020.02.12
[BOJ] 17780. 새로운 게임  (0) 2020.02.03
[BOJ] 1600. 말이 되고픈 원숭이  (0) 2020.02.01
[BOJ] 2532. 반도체 설계  (0) 2020.01.29
[BOJ] 12783. 가장 긴 증가하는 부분 수열 3  (0) 2020.01.29

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

 

1600번: 말이 되고픈 원숭이

첫째 줄에 자연수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있는 곳으로는 이동할 수 없다. 시작점과 도착점은 항상 평지이다. W와 H는 1이상 200이하의 자연수이고, K는 0이상 30이하의 정수이다.

www.acmicpc.net

최소 동작수를 구하는 문제이므로 bfs로 이동할 수 있는 방법을 매번 사용하여 정답을 구하면 된다.

벽 부수고 이동하기 문제와 비슷하게 풀었다.

 

 

인접한 네 방향으로 이동하는 경우와 아직 능력을 k번만큼 사용하지 않았다면 말의 움직임처럼 이동하는 경우를 모두 큐에 넣어주고 이동할 때마다 배열에 동작 수를 저장한다.

 

 

이때 해당칸에 도착할 때 능력을 몇 번 사용했는지에 따라 다르므로 삼차원 배열을 사용한다.

visit[x][y][z] = (x, y) 좌표에 능력을 z번 사용해서 온 동작의 수

 

 

마지막 visit[h-1][w-1][0]부터 visit[h-1][w-1][k]까지 중 최솟값이 정답이다.

(마지막 칸에 도착했을때 능력을 0번 사용한 경우부터 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
109
110
111
112
113
114
115
116
117
118
119
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
 
int k, w, h;
int map[200][200];
 
int dx[] = { 0,0,1,-1 };
int dy[] = { 1,-1,0,0 };
 
int hx[] = { -2-2-1-11122 };
int hy[] = { -11-22-22-11 };
 
int visit[200][200][31];
 
 
struct Pos {
    int x;
    int y;
    int cnt; //능력을 사용한 횟수
    Pos(int a, int b, int c) {
        x = a;
        y = b;
        cnt = c;
    }
};
 
 
void bfs(int x, int y) {
    memset(visit, -1sizeof(visit));
 
    queue<Pos> q;
    q.push(Pos(x, y, 0));
    visit[x][y][0= 0;
 
    int cnt, nx, ny;
    while (!q.empty()) {
        x = q.front().x;
        y = q.front().y;
        cnt = q.front().cnt;
        q.pop();
 
 
        //능력을 아직 사용할 수 있어서 말처럼 움직이는 경우
        if (cnt < k) {
 
            for (int i = 0; i<8; i++) {
                nx = x + hx[i];
                ny = y + hy[i];
 
                //범위 체크
                if (nx < 0 || ny < 0 || nx >= h || ny >= w) continue;
                //장애물 확인
                if (map[nx][ny] == 1continue;
                //방문확인
                if (visit[nx][ny][cnt + 1!= -1continue;
 
 
                //능력을 사용하였으므로 cnt를 증가시킨 채로 큐에 넣는다.
                q.push(Pos(nx, ny, cnt + 1));
                visit[nx][ny][cnt + 1= visit[x][y][cnt] + 1;
            }
        }
 
 
        //인접한 칸으로 이동하는 경우
        for (int i = 0; i<4; i++) {
            nx = x + dx[i];
            ny = y + dy[i];
 
            if (nx < 0 || ny < 0 || nx >= h || ny >= w) continue;
            if (map[nx][ny] == 1continue;
            if (visit[nx][ny][cnt] != -1continue;
 
 
            //능력을 사용하지 않은 경우이므로 cnt는 그대로이다
            q.push(Pos(nx, ny, cnt));
            visit[nx][ny][cnt] = visit[x][y][cnt] + 1;
 
        }
 
 
    }
 
}
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> k;
    cin >> w >> h;
 
 
    for (int i = 0; i<h; i++) {
        for (int j = 0; j<w; j++) {
            cin >> map[i][j];
        }
    }
 
 
    bfs(00);
 
    int ans = 2100000000;
 
    for (int i = 0; i <= k; i++) {
        if (visit[h - 1][w - 1][i] != -1 && ans > visit[h - 1][w - 1][i]) {
            ans = visit[h - 1][w - 1][i];
        }
    }
 
    if (ans == 2100000000cout << -1 << '\n';
    else cout << ans << '\n';
 
 
    return 0;
}
Colored by Color Scripter
 

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

 

2352번: 반도체 설계

첫째 줄에 정수 n(1 ≤ n ≤ 40,000)이 주어진다. 다음 줄에는 차례로 1번 포트와 연결되어야 하는 포트 번호, 2번 포트와 연결되어야 하는 포트 번호, …, n번 포트와 연결되어야 하는 포트 번호가 주어진다. 이 수들은 1 이상 n 이하이며 서로 같은 수는 없다고 가정하자.

www.acmicpc.net

1번 포트부터 n번 포트까지 연결이 꼬이지 않게 최대로 연결해야 하는 문제로 최장 증가수열문제이다.

 

 

12015번 가장 긴 증가하는 부분 수열 2 문제(문제 링크)와 똑같은 코드로 nlogn만에 풀 수 있다.

(12015번 문제 풀이 링크)

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
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int n;
vector<int> vt;
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> n;
    vt.push_back(0);
 
 
 
    int num;
    for (int i = 0; i < n; i++) {
        cin >> num;
        if (num > vt.back()) {
            //벡터의 가장 마지막 값보다 크다면(증가하는 순서) push
            vt.push_back(num);
        }
        else {
            //num이상의 값이 처음으로 나타나는 위치에 num을 넣는다.
            int index = lower_bound(vt.begin(), vt.end(), num) - vt.begin();
            vt[index] = num;
        }
    }
 
 
 
    //처음에 넣어놓은 0을 제외한 벡터의 사이즈가 최장 증가 수열의 길이
    cout << vt.size() - 1 << '\n';
 
    return 0;
}
Colored by Color Scripter
 

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

 

12738번: 가장 긴 증가하는 부분 수열 3

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (-1,000,000,000 ≤ Ai ≤ 1,000,000,000)

www.acmicpc.net

12015번 가장 긴 증가하는 부분 수열 2 문제(문제 링크)와 거의 똑같은 LIS 문제이다. (12015번 문제 풀이 링크)

 

 

 

두 문제의 차이는 수열의 값의 범위뿐이다. 이번 문제에서는 범위가 (-1,000,000,000 ≤ Ai ≤ 1,000,000,000) 으로 처음에 벡터에 넣어놓는 값으로 -1,000,000,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
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int n;
vector<int> vt;
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> n;
    vt.push_back(-2000000000);
 
 
 
    int num;
    for (int i = 0; i < n; i++) {
        cin >> num;
        if (num > vt.back()) {
            //벡터의 가장 마지막 값보다 크다면(증가하는 순서) push
            vt.push_back(num);
        }
        else {
            //num이상의 값이 처음으로 나타나는 위치에 num을 넣는다.
            int index = lower_bound(vt.begin(), vt.end(), num) - vt.begin();
            vt[index] = num;
        }
    }
 
 
 
    //처음에 넣어놓은 0을 제외한 벡터의 사이즈가 최장 증가 수열의 길이
    cout << vt.size() - 1 << '\n';
 
    return 0;
}
Colored by Color Scripter
 

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

 

12015번: 가장 긴 증가하는 부분 수열 2

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000,000)

www.acmicpc.net

LIS 문제이지만 11053번 가장 긴증가하는 부분 수열문제처럼 dp로 풀 수 없다.

dp로 풀면 시간 복잡도가 n제곱이 나오는데 이 문제는 n의 최댓값이 1,000,000이므로 n제곱으로 풀면 시간초과가 난다.

 

 

이분 탐색을 이용하여 nlogn만에 풀 수 있다.

 

 

0. 우선 입력으로 들어오는 최솟값보다 작은 값을 벡터에 넣어놓고 시작한다.

 

 

1. 값을 입력 받는다.

- 벡터의 맨 마지막 값보다 크다면(증가하는 순서라면) 입력받은 값을 벡터에 넣는다.

- 그렇지 않다면 이분탐색으로 벡터에 입력받은 값 이상의 값이 처음으로 나타나는 위치에 입력받은 값을 넣어준다.

(lower_bound를 이용하여 쉽게 구할 수 있다. )

 

 

2. 입력이 끝나면 벡터의 사이즈 -1한 값이 가장 긴 증가하는 부분 수열의 길이가 된다.

(처음에 넣어 놓은 값을 제외하기 위해 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
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
 
int n;
vector<int> vt;
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    cin >> n;
    vt.push_back(0);
 
 
 
    int num;
    for (int i = 0; i < n; i++) {
        cin >> num;
        if (num > vt.back()) {
            //벡터의 가장 마지막 값보다 크다면(증가하는 순서) push
            vt.push_back(num);
        }
        else {
            //num이상의 값이 처음으로 나타나는 위치에 num을 넣는다.
            int index = lower_bound(vt.begin(), vt.end(), num) - vt.begin();
            vt[index] = num;
        }
    }
 
 
 
    //처음에 넣어놓은 0을 제외한 벡터의 사이즈가 최장 증가 수열의 길이
    cout << vt.size() - 1 << '\n';
 
    return 0;
}
hColored by Color Scripter
 

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

 

1965번: 상자넣기

정육면체 모양의 상자가 일렬로 늘어서 있다. 상자마다 크기가 주어져 있는데, 앞에 있는 상자의 크기가 뒤에 있는 상자의 크기보다 작으면, 앞에 있는 상자를 뒤에 있는 상자 안에 넣을 수가 있다. 예를 들어 앞에서부터 순서대로 크기가 (1, 5, 2, 3, 7)인 5개의 상자가 있다면, 크기 1인 상자를 크기 5인 상자에 넣고, 다시 이 상자를 크기 7인 상자 안에 넣을 수 있다. 하지만 이렇게 상자를 넣을 수 있는 방법은 여러 가지가 있을 수 있다. 앞의

www.acmicpc.net

LIS (Longest increasing Subsequence) 최장 증가 수열 문제이다.

 

백준문제 중에 11053 가장 긴 증가하는 부분 수열 문제(문제 링크)와 똑같은 코드로 풀 수 있다. (문제 풀이 링크)

 

 

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>
using namespace std;
 
int arr[1000];
int dp[1000];
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    int n;
    cin >> n;
 
 
    for (int i = 0; i<n; i++cin >> arr[i];
 
 
    for (int i = 0; i<n; i++) {
        //처음엔 자기 자신의 길이인 1
        dp[i] = 1;
 
        //i번보다 앞의 값들과 비교
        for (int j = 0; j<i; j++) {
 
            //j번째 값이 i 번째 값보다 작아야 하고
            //dp값이 현재 가진 값보다 같거나 커야한다.
            if (arr[j] < arr[i] && dp[j] >= dp[i]) {
                //현재 i번째 값이 부분 수열에 추가되므로 길이는 +1이 된다.
                dp[i] = dp[j] + 1;
            }
        }
    }
 
 
    //최댓값을 찾는다.
    int ans = 0;
    for (int i = 0; i<n; i++) {
        if (ans < dp[i]) ans = dp[i];
    }
 
    cout << ans << '\n';
 
    return 0;
}
 
Colored by Color Scripter
 

 

 

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

 

11053번: 가장 긴 증가하는 부분 수열

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오. 예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.

www.acmicpc.net

문제 제목 그대로 가장 긴 증가하는 부분 수열 LIS (Longest increasing Subsequence) 문제이다.

일반적으로 dp 식을 사용해서 풀 수 있다.

 

 

 

dp[i]는 수열의 i번째 값을 마지막으로 하는 가장 긴 증가하는 부분 수열이다.

dp[i]값을 구하기 위해서는 첫 번째 값부터 i-1번째 값( j )까지를 검사한다. -> 시간 복잡도는 n제곱이 된다

(먼저 dp배열은 자기 자신을 부분 수열로 하면 최소 길이가 1이므로 1로 초기화해준다)

 

 

 

1. j번 수열의 값이 i번 수열의 값보다 작고 (증가하는 수열이어야 하므로)

2. dp[j]값은 현재 dp[i]값보다 같거나 커야 한다(가장 긴 수열)

위의 조건을 만족한다면 dp[i] 는 dp[j] + 1 값을 가지게 된다(수열의 길이가 1 늘어남) 

 

 

 

문제에 나온 예시를 그림으로 보면 처음에 첫 번째 값(i가 0)인 10은 최대 길이가 1이므로 1인 상태로 다음 수열로 넘어간다.

 

 

 

 

 

수열의 다음 값(i가 1)은 20인데 앞의 값은 10 밖에 없다. 10은 20보다 작고 dp값도 같기 때문에 dp[i]값은 +1 된다.

 

 

 

그다음 값은 10인데 앞의 값들(10과 20) 중 10보다 작은 값이 없으므로 길이는 그대로 자기 자신인 1이 된다.

 

그리고 i는 증가해서 30 값을 가리킨다. 현재 i가 3이므로 0번부터 2(i-1) 번까지의 값을 보면 된다.

0번부터 2번까지의 값은 각각 10, 20, 10으로 모두 30보다 작다.

이중 dp배열의 가장 큰 값+1이 dp[i]값이 되므로 dp[1]값인 2에서 1을 더한 3이 dp[3]에 저장된다.

 

 

 

 

 

i값이 4가 되고 이제 j는 0부터 3까지의 값을 검사한다.

이 중 20보다 작은 값은 0번째와 2번째이다. 둘 다 10이고 dp의 값도 1로 현재 dp[i]와 같으므로 dp[i]는 dp[j]에 1을 더한 값인 2가 된다.

 

 

 

 

 

마지막 값인 50과 앞의 값들을 비교해보면 모두 50보다 작다. 그러므로 가장 큰 dp값인 dp[3] + 1 한 값인 4가 dp[5]에 저장된다.

 

 

 

 

마지막 값이 작은 값인 경우에는 마지막 위치에 최댓값이 저장되지 않을 수 있으므로 dp배열을 모두 검사해서 최댓값을 찾아야 한다.

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>
using namespace std;
 
int arr[1000];
int dp[1000];
 
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
 
    int n;
    cin >> n;
 
 
    for (int i = 0; i<n; i++cin >> arr[i];
 
 
    for (int i = 0; i<n; i++) {
        //처음엔 자기 자신의 길이인 1
        dp[i] = 1;
 
        //i번보다 앞의 값들과 비교
        for (int j = 0; j<i; j++) {
 
            //j번째 값이 i 번째 값보다 작아야 하고
            //dp값이 현재 가진 값보다 같거나 커야한다.
            if (arr[j] < arr[i] && dp[j] >= dp[i]) {
                //현재 i번째 값이 부분 수열에 추가되므로 길이는 +1이 된다.
                dp[i] = dp[j] + 1;
            }
        }
    }
 
 
    //최댓값을 찾는다.
    int ans = 0;
    for (int i = 0; i<n; i++) {
        if (ans < dp[i]) ans = dp[i];
    }
 
    cout << ans << '\n';
 
    return 0;
}
Colored by Color Scripter
 

'BOJ' 카테고리의 다른 글

[BOJ] 12015. 가장 긴 증가하는 부분 수열 2  (0) 2020.01.29
[BOJ] 1965. 상자넣기  (0) 2020.01.29
[BOJ] 2167. 2차원 배열의 합  (0) 2020.01.24
[BOJ] 2805. 나무 자르기  (0) 2020.01.21
[BOJ] 1654. 랜선 자르기  (0) 2020.01.20

+ Recent posts