aqusiz / ps_practice Goto Github PK
View Code? Open in Web Editor NEWps_practice
ps_practice
함수 set_router: 최소 거리를 받아, 설치 가능한 총 공유기 개수를 반환하는 함수
최소거리에 대한 이진 탐색으로 설치 가능한 공유기 개수가 C를 넘기는지 넘기지 못하는지에 대해 판별
이때 upper bound 탐색
boj 단계별로 풀어보기 12. 집합과 맵 완료
대략 10^8(1억)개의 O(n) 알고리즘 시행시간을 1초로 잡고 계산
O(n^2)의 알고리즘은 1만개 정도의 케이스를 1초에 계산 가능
python의 set 자료형은 hash table로 구현되어있다.
즉, in 을 이용한 원소 찾기를 O(1)에 가능
import sys
sys.stdin.readline()
이때, \n까지 함께 읽어들이기 때문에 필요없다면 rstrip()으로 벗겨낼 수 있다.
사이클이 없는 방향 그래프(DAG)에서 vertex 간의 순서를 선형으로 정렬하는 것
인접 리스트 사용 시
in_degree 배열을 사용(해당 vertex로 들어오는 edge의 수)
인접 리스트 사용 시
결국 모든 지점에서 bfs로 가능한 경로를 확인해야 함
1회 bfs ->
각 tile마다 실행하므로
시간 초과가 발생한 이유
from collections import deque
q = deque()
q.append()
q.popleft()
while q:
y, x, dist = q.popleft()
if visited[y][x]:
continue
visited[y][x] = True
....
option) 가능한 최대 거리는 결국 맨 왼쪽 위 -> 맨 오른쪽 아래 = H + W - 2
최대 거리 도달 시 즉시 종료하도록 최적화 가능 (ex: 바다 없이 모든 타일이 육지인 경우)
k가 몇이냐에 따라 방문 가능한 위치의 상태가 계속 바뀌기 때문에 단순한 2차원 visit 배열로는 해결 불가
따라서 3차원 visit 배열 사용
visited[y][x] -> visited[k][y][x]
정수 배열의 구간 합을 구하는 경우, 누적합 배열을 활용하면
이때 구간 별 누적합을 저장하는 세그먼트 트리를 활용하면 구간 합 구하기와 값 업데이트를 모두
간단하게는 원본 배열의 크기의 4배를 할당한다.
- 원본 배열의 크기가
- 세그먼트 트리의 높이
void init(vector<lli>& seg, vector<lli>& arr, int pos, int l, int r) {
// left == right인 경우는 리프 노드이므로 값을 그대로 저장한다.
if(l == r) {
seg[pos] = arr[l];
} else { // 리프 노드가 아닌 경우 왼쪽 서브트리와 오른쪽 서브트리를 재귀적으로 초기화한 후 양쪽 값을 더해준다.
init(seg, arr, pos*2, l, (l+r)/2);
init(seg, arr, pos*2 + 1, (l+r)/2 + 1, r);
seg[pos] = seg[pos*2] + seg[pos*2 + 1];
}
}
총 4가지 경우로 조건을 분기할 수 있다. ([left, right]은 구간 합을 구하길 원하는 구간, [start, end]는 세그먼트 트리의 현재 노드가 포함하는 구간)
목표 index가 [start, end] 구간에 포함하는지 아닌지로 조건을 분기할 수 있다.
구간의 합 뿐만 아니라 구간의 최소값, 최대값, XOR 등등을 모두 구할 수 있다.
ex)최소값을 구하는 경우, +로 계산되는 모든 부분을 min으로 고치면 된다.
완전 이진 트리. 모든 부모는 자신의 자식보다 작아야 한다(최소 힙. 큰 경우 최대 힙)
배열로 구현. left_child = parent * 2, right_child = parent * 2 + 1
어려웠던 문제: 이중 우선순위 큐
최소 힙과 최대 힙을 함께 사용
이때, 최소 힙에서 삭제되는 값들을 큰 순서대로 저장하고, 최대 힙에서 삭제되는 값들은 작은 순서대로 저장해둔다.
삭제된 값이 힙에서 발견되면 삭제 처리해준다.
int *ptr = new int; // 단일 변수의 동적 할당
int *ptr1 = new int(6); // 단일 변수의 동적 할당 및 초기화
int *ptr2 = new int { 6 }; // 단일 변수의 동적 할당 및 유니온 초기화
delete ptr;
bool *visited = new bool[N+1];
for(int i = 1; i <= N; i++) visited[i] = false; // 반드시 따로 초기화 해줄 것!!!
...
delete[] visited;
16235 나무 재테크
16235 코드(TLE in python 3, AC in pypy)
어린 나무부터 양분을 먹는다는 조건 때문에 heapq를 사용하였지만, 각 처리마다 정렬을 해주는 건 시간을 많이 쓴다.
deque을 사용해도 되는 이유 --> 처음 주어지는 나무는 모두 다른 위치에 1개씩 주어지기 때문에, popleft, appendleft, append를 적절히 사용하면 정렬하지 않고도 정렬된 상태를 유지할 수 있다.
1967 트리의 지름(n=10,000)
1967 코드
1167 트리의 지름(n=100,000)
1167 코드
각 leaf node마다 dfs로 가장 먼 node 찾기 ->
양방향 그래프로 만든 다음, bfs를 2번 사용 ->
void go(int n, int mask) {
int new_mask;
for(int i = 0; i < N; i++) {
if(mask & (1 << i)) continue;
if(w[n][i] == 0) continue;
new_mask = mask | (1 << i);
// dp는 모두 INF로 초기화 되어 있음. 최소값 갱신 발생 시 해당 지점 재탐색
if(dp[i][new_mask] > dp[n][mask] + w[n][i]) {
dp[i][new_mask] = dp[n][mask] + w[n][i];
if(new_mask != done) go(i, new_mask);
}
}
}
// main에서 dp[i][done] + w[i][0]을 계산하여 최소값 탐색
int go(int n, int mask) {
// 모든 정점을 지나온 상태라면 n -> 0 weight 반환
if(mask == done) return (w[n][0] == 0) ? INF : w[n][0];
// 이미 탐색한 적 있는 정점이라면, 메모이제이션 값을 그대로 반환
if(dp[n][mask] != -1) return dp[n][mask];
int new_mask;
dp[n][mask] = INF;
for(int i = 0; i < N; i++) {
if(mask & (1 << i)) continue;
if(w[n][i] == 0) continue;
new_mask = mask | (1 << i);
dp[n][mask] = min(dp[n][mask], w[n][i] + go(i, new_mask));
}
return dp[n][mask];
}
가중치가 0 또는 1로만 구성된 그래프에서 다익스트라 알고리즘보다 빠르게 최단 경로를 찾을 수 있는 알고리즘
deque을 사용하여 구현
위 방식을 사용하면, 가중치가 1인 간선을 0번 거쳐간 노드 -> 1번 거쳐간 노드 -> ... -> n번 거쳐간 노드 순서로
비용이 적은 경로부터 탐색하기 때문에, 거쳐간 간선을 다시 거쳐가는 경우가 없으므로
같은 이유로 모든 정점을 한 번씩만 거쳐가기 때문에
그러므로
2533 사회망 서비스(SNS)
2533 코드 1
2533 코드 2
코드 2 참고 풀이
트리: 사이클이 없는 그래프
그래프에서처럼 dp 기법 활용 가능(다익스트라 알고리즘처럼...)
dfs를 활용해 탐색하면서 서브트리에서의 답을 구한 후, 그 답을 활용해 트리의 답을 구해나가는 방식
입력의 끝을 구분 짓는 방법
cin을 이용한 입력: 버퍼를 사용하기 때문에, Enter 키를 누르기 전까지 프로그램에 전달 X
cin.get() 멤버함수를 이용한 입력 (overloading을 이용해, 0~2개의 매개변수를 가진다)
ch = cin.get();
cin.get(ch1).get(ch2); // cin.get(ch1)이 cin 객체를 반환하기 때문에 get(ch2)에 적용되는 cin 객체로 다시 활용
char temp[30];
cin.get(temp, 30);
while(cin && cin.get != '\n') continue;
cin.eof() : 는 EOF가 탐지되었을 경우 True 값을 리턴하고, 그 이외에는 False 값을 리턴한다.
cin.fail() : eofbit나 failbit 둘 중 하나라도 값이 1로 설정되어 있으면 True 값을, 그 이외에는 False 값을 리턴한다.
cin.clear() : EOF 플래그를 지우고 다시 입력이 진행될 수 있도록 허용한다. (입력 Buffer를 초기화하는 역할을 한다.)
문제 자체는 모든 케이스를 탐색해보면 답을 구할 수 있다.(모든 케이스가
연속한 두 원소가 같을 때 하나로 병합하고 값을 2배로 늘리는 구현
void merge(vector<int>& v) {
vector<int>::iterator cur, next;
for(cur = v.begin(); cur != v.end();) {
next = cur + 1;
// next가 v.end()일 때의 예외처리 필수
if(next != v.end() && *cur == *next) {
cur = v.erase(cur); // erase(vector<T>::iterator pos)는 해당 pos를 지운 뒤, 그 다음 iterator를 반환
*cur *= 2;
}
cur++;
}
}
이차원 vector 전체를 parameter로 넘겨 받는 방식을 통해 간단하게 이차원 vector를 통째로 조작하는 backtracking을 구현
vector<vector<int> > up(vector<vector<int> > b, int depth) {
...
return b;
};
vector<vector<int> > down(vector<vector<int> > b, int depth);
vector<vector<int> > left(vector<vector<int> > b, int depth);
vector<vector<int> > right(vector<vector<int> > b, int depth);
void simulate(vector<vector<int> > b, int depth) {
if(depth == 5) return;
simulate(up(b), depth+1);
simulate(down(b), depth+1);
simulate(left(b), depth+1);
simulate(right(b), depth+1);
}
10986-나머지 합
구간 i, j의 구간 합을 M으로 나눈 나머지가 0이라는 것은,
(psum[j] - psum[i-1])%M = 0이라는 것
N=5, M=3일 때
1 2 3 1 2 라는 수열에서, 구간 합 배열을 구하면 1 3 6 7 9
이를 M으로 나눈 나머지의 배열은 1 0 0 1 0
0 + 1 + 2 + 1 + 3 = 7
(빈 배열의 합이 0이므로, 값 0은 1부터 세서 더한다. 하지만 값 1은 2번 등장하고 나서부터 조건에 맞는 구간이 형성되므로 0부터 세서 더한다)
재귀를 이용한 구현
https://www.acmicpc.net/source/46833174
두 양의 정수 a, b(a > b)에 대해, a = bp + r 이라면
a, b의 최대공약수 = b, r의 최대공약수 ( gcd(a, b) == gcd(b, r) )
r = 0이 될 때까지 재귀적으로 나누어 내려가면, 그 때의 b'가 곧 a, b의 최대공약수가 된다.
ex) 12345와 1234의 최대공약수
12345 % 1234 = 5
1234 % 5 = 4
5 % 4 = 1
4 % 1 = 0
따라서 gcd(12345, 1234) = 1
시간복잡도
나눗셈 횟수 = O(log a), 정수의 나눗셈의 복잡도 = O(log a)
전체적으로 O((log a)^2)
1. find() + substr()
// example
current= str.find(',');
//find는 찾을게 없으면 npos를 리턴함
while(current!=string::npos){
string substring=str.substr(previous,current-previous);
x.push_back(substring);
previous = current+1;
current=str.find(',',previous);
}
x.push_back(str.substr(previous,current-previous));
// 문제에서의 활용(n번 반복: 0번을 포함)
curr = arr.find(",");
for(int i = 0; i < n; i++) {
integer = arr.substr(prev, curr - prev);
dq.push_back(stoi(integer));
prev = curr + 1;
curr = arr.find(",", prev);
}
2. istringstream + getline()
// example
#include<iostream>
#include<string>
#include<vector>
#include<sstream>
using namespace std;
int main()
{
string str="java c c++ python";
istringstream ss(str);
string stringBuffer;
vector<string> x;
x.clear();
cout<<"어떻게 잘리는지 확인해봅시다 ->";
//구분자가 , 이라면 getline(ss, stringBuffer, ',')쓰면됨
while (getline(ss, stringBuffer, ' ')){
x.push_back(stringBuffer);
cout<<stringBuffer<<" ";
}
cout<<endl<<"vector 값을 출력해보자."<<endl;
for(int i=0;i<x.size();i++){
cout<<x[i]<<endl;
}
return 0;
}
각 동전의 가치가 배수 관계인 경우, 그리디 알고리즘으로 해결 가능 (11047 동전 0)
하지만 본 문제처럼 배수 관계라는 조건이 없을 경우, dp로 해결
dp[0] = 1;
// 각 동전마다 갱신
for(int i = 0; i < n; i++) {
// 현재 동전의 가치부터 목표 값 k가지 갱신
for(int j = coin[i]; j <= k; j++) {
// 점화식
dp[j] += dp[j - coin[i]];
}
}
최댓값과 최솟값을 동시에 구해야 하는 문제
단순하게 dp를 위한 배열을 2개 만들 경우 메모리 초과 (
1차원 dp -> 바로 직전 정보만 알고 있으면 다음 단계로 진행 가능 -> sliding window 방식 적용하여 메모리 최적화 가능
매 반복문에서 다음 단계를 위한 dp배열을 새로 초기화해주어야 한다. 그렇지 않으면 이전 단계에 계산한 값이 남아있어 결과에 영향을 줌.
// solve: dp
for(int i = 1; i < n; i++) {
// 단계 진행할 때마다 배열을 초기화
for(int j = 0; j < 3; j++) {
dp_min[i % 2][j] = INT32_MAX;
dp_max[i % 2][j] = -1;
}
for(int j = 0; j < 3; j++) {
...
12015 가장 긴 증가하는 부분 수열 2
12015 코드
참고한 풀이
11053 가장 긴 증가하는 부분 수열 문제의 경우 dp를 이용해
하지만 위 문제는
어떤 정렬된 배열에서, 특정 값 val의 lower bound = 정렬된 상태를 유지하면서 val을 삽입할 수 있는 가장 작은 index
이분 탐색을 이용해
C++의 경우, 헤더에서 lower_bound 함수를 지원 한다 (해당 배열에서 lower bound에 해당하는 자리의 pointer를 반환. 없을 경우 last 반환)
14002 가장 긴 증가하는 부분 수열 4
14002 코드
해당 방법으로는 LIS를 구성하는 실제 원소들을 알 수는 없다. 실제 LIS를 구하고 싶다면, 따로 index를 저장하는 배열을 저장한 후 끝에서부터 탐색하는 방식으로 구하여야 한다.
입력되는 수열 A의 원소
(그렇지 않은 코드의 경우 길이가 1 작게 나오거나, LIS를 구현하는 과정에서 index가 틀릴 수 있다.)
float point = 12.3456;
cout << fixed; // "소수점 이하" 자릿수를 고정하겠다
cout.precision(1); // 1자리로 고정하겠다 (fixed 없이 사용할경우, 정수부를 합친 전체 자릿수가 고정됨)
// 이후 출력 되는 모든 소수들은 소수부가 한 자리로 고정 됨
cout << point; // 12.3 출력
cout.unsetf(ios::fixed); // fixed 해제
DP를 응용한 dfs
그리디 알고리즘: 가장 가치가 높은 보석부터 하나씩 가방에 담는다
보석은 priority_queue를 이용한 max heap, 가방은 multiset을 이용한 BST로 저장 후 탐색
multiset인 이유는 무게 한도가 같은 가방이 중복으로 들어올 수 있기 때문
보석을 담을 수 있는 가장 작은 무게의 가방을 탐색하기 위해 이분탐색(lower_bound) 활용
이때, <algorithm>
의 lower_bound(start, last, k)
함수를 이용할 경우, 인자를 넘기는데
따라서 multiset의 member function인 lower_bound를 사용하여야 효율적으로 탐색 가능하다
Call by Reference example
void func(int a, queue<int> &q) {
q.push(a);
return;
}
int main() {
queue<int> q;
func(10, q);
cout << q.front() << endl; // print 10
}
1부터 n까지 직접 나눠보기 -> O(n)
1부터 sqrt(n)까지 나눠본 후, 그렇게 나온 값들을 이용해 다시 n을 나누어 구하기(중복제거) -> O(√N)
가령 10!에서 2가 몇번 곱해졌는지 구하려고 할 때,
10 // 2 = 5 -> 2 4 6 8 10 총 5개
10 // 4 = 2 -> 4 8 총 2개
10 // 8 = 1 -> 8 총 1개
즉, 10!은 2가 총 8번 곱해진 수이다.
같은 방식으로 5의 개수를 구해 10의 몇승이 곱해졌는지 구하면 0의 개수를 구할 수 있다.
2981 검문
N개의 숫자가 주어졌을 때, M으로 나눈 나머지가 모두 같은 숫자 M을 모두 찾는 문제
N_1 = M * a_1 + R
N_2 = M * a_2 + R
N_3 = M * a_3 + R
...
각각의 차를 구하면 R이 소거되어, 이들의 최대공약수를 유클리드 호제법으로 구하고 그의 약수를 모두 찾으면 된다.
가중치가 없는 최단 경로를 찾는 문제: dfs는 불가능, 무조건 bfs
벽을 일일히 하나씩 부숴보는 브루트 포스 방식은 (N*M)^2의 시간 복잡도가 소요. 불가능
지금까지의 일반적인 bfs처럼 칸마다 방문체크를 하는 방식만으로는 불가능
그렇다면 어떻게 할 것인가?
벽을 부순 세계의 경로는 벽을 부수지 않은 경로와는 무관하다. 벽을 부수지 않은 경로에서 이미 지나왔던 길도 중복체크 해야 함. 또한 어떤 경로로든 이전에 지나간 적이 있다면 무시
ex)
5 6
000000
011110
010000
010111
100000
visited[i][j][0](벽을 부순적 없는 경로)는 일반적인 bfs와 크게 다를 바 없지만
visited[i][j][1]의 경로는 벽을 부순 다음 다시 부수는 경우를 제외하면 모두 가능하기 때문에,
(2,5) 또는 (3,5)처럼 중첩되는 경우가 존재할 수 있다.
input: 2개의 문자열 a, b
각 문자열의 맨 앞에 0을 끼워넣은 2차원 행렬 생성
dp[i][j] == a[:i-1], b[:j-1] 문자열의 최대 LCS 길이
dp[i][j]를 구하기 위해,
if a[i-1] == b[i-1]
이전까지의 최장 LCS 길이 + 1 (즉, dp[i-1][j-1] + 1)
else
max(좌, 상) (즉, max(dp[i-1][j], dp[i][j-1])
주어진 수열에 대해 구간 합을 구하는 것은 O(n)
이때 한 번 누적 합 배열(prefix sum array)을 구해두면, 각 구간 쿼리에 대해 O(1)만에 구간 합을 구할 수 있다
psum[i] = 처음부터 i번째 수까지의 합
i번째 수부터 j번째 수까지의 합 = psum[j] - psum[i-1]
구현할 때 고려할 것
구슬끼리 만나서 이동하는 경우
파란 구슬이 빠지면 빨간 구슬이 빠져도 무시
파란 구슬이 빠진다고 끝이 아니라, 다른 경우를 더 찾아야 함
꼭 빨간 구슬만 움직일 수 있어야 하는 건 아님
visited는 사용 불가 (복잡하게 움직이는 경우)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.