Open SHEWANTSME opened 2 years ago
이 이슈를 만들었지만 정작 시작을 안했었구나,, ㅎㅎ
12월의 중간까지 왔지만,, 그동안 백준 티어 올리느라 제대로된 공부를 하지 않은것 같다.
그래서 새롭게 STL이랑 그 이상으로 알아야 할 알고리즘들을 정리해볼까 한다..
일단 기본적으로 알아야 할 STL내용을 적어보자면
공부순서
// 이거까지 해도 최소 플레~다이아임
너의 상태를 보자면, 앞부분에서 뚫린 빵꾸를 제대로 매꾸지 않고 뒤에서 어정쩡하게 배우느라 존나 노답 그 자체임..
그래서 다시 basement부터 차근차근 할 필요성이 존나게 크다고 봄
각설하고
c++에서 내가 다시 다져야할 내용, stl이 뭐냐면
1.vector
https://jasonyoo.tistory.com/45 -> 일단 stl 주요 내용 정리해둠 잘 읽어보쟝
ex 1 :> 승철이문제
승철이는 뇌를 잃어버렸다. 학교에 갔더니 선생님이 자연수로 이루어진 N개의 카드를 주며 M개의 질문을 던진다. 그 질문은 나열한 카드 중 A번째부터 B번째까지의 합을 구하는 것이다. 뇌를 잃어버렸기 때문에 승철이는 이 문제를 풀 수 없다. 문제를 풀 수 있는 프로그램을 작성해보자.
입력
수의 개수 N, 합을 구해야 하는 횟수 M, 그 이후 N개의 수가 주어진다. 수는 100 이하의 자연수. 그 이후 M개의 줄에는 합을 구해야 하는 구간 A, B가 주어진다.
출력
M개의 줄에 A부터 B까지의 합을 구하라.
범위
1 <= N <= 100,000
1 <= M <= 100,000
1 <= A <= B <= N
예제입력
8 3 1 2 3 4 5 6 7 8 1 4 1 5 3 5 에제출력
이렇게 풀면 시간초과 남
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100004], b, c, psum[100004], n ,m;
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
for(int i = 0 ; i < m; i++){
cin >> b >> c;
int sum = 0;
for(int j = b; j <= c; j++) sum += a[j];
cout << sum << '\n';
}
return 0;
}
그래서 이렇게 dp로 풀거나 구간합을 사용해야함
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100004], b, c, psum[100004], n ,m;
int main(){
ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
psum[i] = psum[i - 1] + a[i];
}
for(int i = 0 ; i < m; i++){
cin >> b >> c;
cout << psum[c] - psum[b - 1] << "\n";
}
return 0;
}
이게 구간합을 쓴거고(위에코드)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int dp[100000];
int main() {
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (i == 1) dp[i] = x;
else {
dp[i] = x + dp[i - 1];
}
}
for (int i = 1; i <= m; i++) {
int y, z; cin >> y >> z;
if (y == 1) cout << dp[z] << endl;
else {
cout << dp[z] - dp[y - 1] << endl;
}
//cout << dp[z] - dp[y] << endl;
}
return 0;
}
위의 코드가 dp를 쓴건데 뭐 거의 같은 로직임
2번째 문제
// 문제 2.
#include <iostream>
#include<string>
#include <vector>
#include <stack>
#include <deque>
using namespace std;
int main() {
string dopa = "life is limited";
stack<char> stk;
deque<char>dq;
for (int j = 0; j < 3; j++) cout << dopa[j];
cout << endl;
for (int i = 0; i < dopa.size(); i++) {
stk.push(dopa[i]);
dq.push_back(stk.top());
}
while (!stk.empty()) {
cout << stk.top() ;
stk.pop();
}
cout << endl;
string dopaa = "dopa!!";
for (int i = 0; i < dopaa.size(); i++) {
dq.push_front(dopaa[i]);
}
while (!dq.empty()) {
cout << dq.back();
dq.pop_back();
}
return 0;
}
나는 deque, stack, string까지 총동원해서 그냥 무지성으로 짰는데
그냥
#include<bits/stdc++.h>
using namespace std;
string dopa = "life is limited";
int main(){
//문자열에서 부분배열(이 부분만을 끄집어낼 수 있겠죠?)
cout << dopa.substr(0, 3) << "\n";
// 반대로
reverse(dopa.begin(), dopa.end());
cout << dopa << "\n";
// 추가한다.
dopa += "dopa!!";
cout << dopa << '\n';
return 0;
}
SUBSTR -> 짤라주는놈
REVERSE -> STRING을 돌려주는놈
STRING은 + - 연산 가능함 ㅎㅎ
이렇게 깔끔하게 나온다니.. ㅅㅂ
그러면 이렇게 나옴
/ lif detimil si efil detimil si efildopa!! /
// 참고로 STRING 관련 정리된 블로그는 https://blockdmask.tistory.com/333 https://blockdmask.tistory.com/338 이니까 잘 찾아보시게
하.. 진짜 존나 빡친다. 개 잘정리해놨는데 다 날라감..
다시 적어보자 씨바
(중복순열, 중복조합도 나중에 적을 예정)
맞다. 니가아는 그 순열 맞다
1.1 STL 사용하기 -> next_permutation과 prev_permutation 이 있다. -- > next는 오름차순, prev는 내림차순으로 먼저 sorted되어 있어야 한다
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(){
// 5P2 출력 하는 코드
vector<int> v = {1, 2, 3, 4, 5};
int n = v.size(); // 5
int r = 2
do
{
for(int i = 0; i < r; i++)
cout << v[i] << " ";
cout << '\n';
reverse(v.begin() + r, v.end()); // 이게 굉장히 중요한데, reverse를 안시키면 nPn만 가능한 next_permu에서
// r만큼 떨어진곳부터 reverse 시켜주니까 역순으로 되어서 거기서부터는 아예 쳐다도 안보는거지!
}while(next_permutation(v.begin(), v.end()));
}
참고로 nPn이면 그냥 reverse 없애고 r자리에 n 넣으면 됨
아 그리고 next_permutation쓸때,
int a[10];
int main() {
for (int i = 0; i < 9; i++) {
cin >> a[i];
}
sort(a, a + 9);
do {
int sum = 0;
for (int i = 0; i < 7; i++)sum += a[i];
if (sum == 100) break;
} while (next_permutation(a, a + 9));
for (int i = 0; i < 7; i++) cout << a[i] << endl;
return 0;
}
이렇게 arr로 쓰면 이런 느낌으로 하면 됨.
prev_permutation은 내림차순이고, 그냥 똑같은 논리로 하면 된다.
1.2 배열로 방문 check -> MOST COMMON WAY TO SOLVE THESE KIND OF PROBLEM
#include <iostream>
#include <vector>
using namespace std;
void Permutation(vector<bool> visited, vector<int> arr, vector<int> perm, int depth)
{ // 방문체크배열, 처음원소들어있던 배열, 최종 답이되는배열, 깊이
if (depth == perm.size()) // r
{ // 깊이가 == r(perm.size())일때
for (int i = 0; i < perm.size(); i++)
cout << perm[i] << " ";
cout << endl;
//출력
}
// 다른경우일때는
for (int i = 0; i < arr.size(); i++)
{
if (visited[i]) continue;// visited[i] == true이면 건너뛰고
visited[i] = true; // 방문 체크
perm[depth] = arr[i];// 방문체크된 인덱스의 값이 perm[depth]
Permutation(visited, arr, perm, depth + 1);
visited[i] = false; // 방문 해제
}
}
int main()
{ // 지금은 3p3이라 이렇게 하는거지 char로 바꾸던 원소개수늘리던.. 다 됨
vector<int> v = { 1,2,3};
int n = v.size();
int r = 3;
vector<int>perm(r); // 최종 답이 되는 배열 ( 원래배열은 그 상태 유지해야하니까 새롭게 하나 make)
vector<bool> visited(n); // 방문한것 체크
Permutation(visited, v, perm, 0); // 3P3 배열의 모든 원소 출력
return 0;
}
이해하기가 어렵긴 하지만 , perm[depth]랑 arr[i]가 항상 같은게 아니라서
일반적인 swap으로 하는 방식은 순서가 일정하지 않을 수 있어서 비추..
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> v;
void Perm(int n, int r, int depth) {
if (r == depth) {
for (auto &ele : v) {
cout << ele << " ";
}cout << endl;
}
else {
for (int i = depth; i < n; i++) {
swap(v[i], v[depth]);
Perm(n, r, depth + 1);
swap(v[i], v[depth]);
}
}
}
int main() {
v[0] = 1;
v[1] = 2;
v[2] = 3;
Perm(3, 3, 0);
return 0;
}
이게 swap이용한 방식
하.. 피곤하니까 나중에 정리하자 https://ansohxxn.github.io/algorithm/combination/
정점 ->Vertex 간선 ->Edge
Edge(간선) -> 이어주는놈 Vertex(정점) ->이음을 받는 놈 (Tree에서 노드라고도 함)
이런경우는 단방향
이런경우는 양방향이겠져
이렇게 나가는edge를 : OUTDEGREE라고 하고 들어오는 edge를 INDEGREE라고 함
이게 완전정리
Vertex랑 Edge 사이에 드는 비용 우리집에서 원캐까지 가는 시간이 10분 ->Cost = 10;
->Cycle이 있으면 안되는 그래프!! (빙빙도는 형태가 되면 안됨) -> 무방향일때도 있고 방향이 있을때도 있음 -> 방향 있을때는 (DAG)라고 함(Directed Acyclic Graph) (밑의 그림이 DAG)
위의 놈처럼 이렇게 생김
--> DP 문제 풀 때 반드시 DAG가 쓰임! 알아두셈 ㅎ
백준 1697번풀때 -> 이거 dp아닌가? 싶었지만
DP는 DAG에 대해서만 사용해야 합니다. 이 문제처럼 정점의 방문 선후 관계가 명확하지 않은 경우에는 사이클에 의해 잘못된 답을 구할 수 있게 됩니다. 라고 하심 ㅎ
이렇게 생긴놈을 뜻함
노드의 왼쪽 subtree에는 반드시 노드보다 작은 값들이 오른쪽 subtree에는 노드보다 큰값들이 존재함!!
이런식으로 보면 알거라고 본당 (위 그림은 CC가 3개임!)
GRAPH 표현 방법!
인접행렬
bool a[1004][1004];
for(int i = 0;i < V; i++){
for(int j = 0; j < V; j++){
if(a[i][j]){
cout << i << "부터 " << j << "까지 경로가 있습니다.\n";
}
}
}
이렇게 vertex와 edge의 관계를 나타내는 정사각형 행렬임. Mostly we use it as a boolean matrix ex) a[1][3] =1 ( 1에서 3으로 가는 경로 exist) 시복 :) O(V^2) 공복 :) O(V^2)
인접리스트
vector<int> adj[1004];
adj[1].push_back(2);
adj[1].push_back(3);
for(int i = 0;i < V; i++){
for(int there : adj[i]){
}
//위와 아래 코드는 의미가 같습니다.
for(int j = 0; j < adj[i].size(); j++){
int there = adj[i][j];
}
}
위와 같이 쓰이며, 3번노드 -> 5번노드 는 adj[3].push_back(5); 이렇게 씀
시복 :) O(V+E) 공복 :) O(V+E)
const int dx[4] = {0,1,-,1,0};
const int dy[4] = {1,0,0,-1};
여기서 방향의 제한을 if문으로 걸면 되겠죠? 문제마다 다르겠지만?
#include <bits/stdc++.h>
using namespace std;
vector<int> adj[1004];
int main(){
adj[1].push_back(2);
adj[1].push_back(3);
for(int there : adj[1]){
cout << there << " ";
}
return 0;
}
// 2 3
깊이 우선탐색은 그래프를 탐색할 때 쓰이는 알고리즘으로써 특정한 노드에서 가장 멀리 있는 노드를 먼저 탐색하는 알고리즘입니다.
주어진 맵 전체를 탐색하며 한번 방문한 노드는 다시한번 방문하지 않기 때문에 만약 인접리스트로 이루어진 맵이면 O(V + E)이고 인접행렬의 경우 O(V^2)가 되졍
Pseudo code
DFS(G, u)
u.visited = true // u 원소는 visited배열에서 true
for each v ∈ G.Adj[u] // Adj배열의 모든원소를 for
if v.visited == false // visited해쓰면 false
DFS(G,v) // 아니면 DFS재귀
그림으로 보면 다음과 같다.
4 - 5 - 3 - 6 - 2 - 7 - 10 - 11 - 9 - 12 - 8 - 1
DFS를 구현하는 2가지 방법
void dfs(int here){
visited[here] = 1;
for(int there : adj[here]){
if(visited[there]) continue;
dfs(there);
}
}
2 . 일단 호출
void dfs(int here){
if(visited[here]) return;
visited[here] = 1;
for(int there : adj[here]){
dfs(there);
}
}
일단 방문이 되어있던 안되어있던 호출부터 함 -> 방문되어있다면 return해서 함수 종료시킴
BFS는 그래프를 탐색하는 알고리즘이며 노드에서 시작해 이웃한 노드들을 먼저 탐색하는 알고리즘이자 같은 가중치를 가진 그래프에서 최단거리알고리즘으로 쓰입니다. 시간복잡도는 DFS와 같으며 주어진 맵전체를 탐색하며 한번 방문한 노드는 다시한번 방문하지 않기 때문에 만약 인접리스트로 이루어진 맵이면 O(V + E)이고 인접행렬의 경우 O(V^2)가 됩니다.
참고로 가중치가 다른 그래프 내에서 최단거리 알고리즘은 다익스트라, 벨만포드 등을 써야 합니다.
--> BFS는 queue가 무조건 필요함 , 물론 visited배열도 필요하지요.
BFS를 구현하는 2가지 방법
BFS(G, u)
u.visited = true // u원소는 방문했다고 함
q.push(u); // queue에 u를 push
while(q.size()) // 만약 q가 empty하지 않을때까지 = while(not q.empty())
u = q.front() //queue의 맨앞놈을 u 라고 하고
q.pop() // 빼버린다음
for each v ∈ G.Adj[u] // u를 중심으로 인접노드 탐색.
if v.visited == false // 방문 안한놈을
v.visited = true // 방문했다고 하고
q.push(v) // queue에 push
(거의 비슷함 위에거랑) -> We do use this code style mostly.
BFS(G, u)
u.visited = 1
q.push(u);
while(q.size())
u = q.front()
q.pop()
for each v ∈ G.Adj[u]
if v.visited == false
v.visited = u.visited + 1 // 이거가 달라진거임
q.push(v)
최단거리 배열을 v.visited = u.visited +1 로 함으로써, 지금 있는곳에서 +1을 하면 가야할 곳 까지의 최단거리가 되겠구나!를 인식해야함. -> which means you can search through level(height)
이렇게 말이야 ㅎㅎ.
이거를 보면 알수 있지 그러면 최종적으로
이렇게 되고 가중치가 같은 최단거리 ---> BFS!! 항상 염두해 두자!
최종정리 :
-->
이름 | 설명 |
DFS | 메모리 덜씀 , 절단점등 문제에 사용, 스택오버헤드 조심!, 코드 더 짧음 , 완탐에서 주로 씀 |
BFS | 메모리 더씀, 가중치 같은 그래프의 최단거리 , 코드 더 김 |
종만북을 시작해볼까.. ㅠ