brew services start postgresql 명령어의 경우 Homebrew를 통해 설치된 PostgreSQL DB 서버를 macOS에서 백그라운드 서비스로 시작하는 명령어이다. 이 명령어는 시스템 부팅 시 자동으로 시작되도록 설정하며, 사용자가 명시적으로 서버를 중지하거나 시스템을 종료하지 않는 한, 계속 실행된다. 따라서 데이터베이스를 사용할 때마다 수동으로 서버를 시작할 필요가 없어서 굉장히 편리하다.
계속 실행되고 있다는 점에서, 메모리와 CPU 사용 측면에서 비효율적인 것이 아닌가 찾아보았다. 하지만 실제 데이터베이스 요청이 들어오지 않으면 CPU 사용량은 거의 없으며, 매우 큰 데이터베이스가 연결되지 않는 한 메모리 사용량은 굉장히 적다고 한다.
하지만 그럼에도 불편함을 느낀다면, 다음 stop 명령어를 통해서 사용하지 않을 때는 꺼둘 수 있다.
터미널에서 PostgreSQL에 접속하기 위해, psql 명령어를 사용한다. '-U postgres'는 PostgreSQL의 기본 관리자 계정이다.
그런데 나는 다음과 같은 에러 사항이 발생했다.
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL: role "postgres" does not exist
postgre라는 이름의 역할이 존재하지 않아 발생하는 문제이다. 이는 PostgreSQL을 업그레이드하거나 새로 설치하면서 데이터베이스 설정이 초기화된 경우 사용자 postgres가 사라질 수 있다고 한다. 또는 새로 설치된 PostgreSQL 인스턴스에서는 postgres 역할이 자동 생성되지 않았을 수 있다고 한다.
1) 디렉토리 권한 수정
/usr/local/var 디렉토리에 쓰기 권한을 부여하였다. 해당 디렉토리를 생성하고, 현재 사용자에게 해당 디렉토리에 대한 소유권을 부여한다.
만약 -d 옵션을 사용하지 않으면, psql은 기본적으로 현재 시스템 사용자와 동일한 이름의 데이터베이스에 연결을 시도한다.
6) 데이터베이스 및 테이블 생성
CREATE DATABASE guestbook;
\c guestbook
CREATE TABLE guestbook (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
생성 후에는 다음 명령어로 테이블 목록을 확인할 수 있다. \dt
이외에도 유용한 명령어들을 정리해보겠다.
명령어
설명
\l ,\list
데이터베이스 목록 나열
\dt
현재 연결된 데이터베이스의 테이블 목록 표시
\d table_name
지정한 테이블의 스키마(구조)를 표시
\d+ table_name
테이블의 상세 정보를 포함한 스키마 표시. 테이블의 칼럼, 인덱스, 제약족건 등에 대한 많은 정보 볼 수 있음.
=> 실제로 코드 쓸 때는, 내 점수를 현재 카드 점수 + min(근우가 왼쪽 카드 선택하고 남은 점수, 근우가 오른쪽 카드 선택하고 남은 점수) 로 계산함.
C++전체 코드
1. 위 로직대로, 전체 점수에서 명우가 더 큰 값을 가져간다는 로직
#include <iostream>
#include <vector>
#include <cstring> //memset
using namespace std;
int dp[1001][1001]; //dp테이블 정의
int sum[1001]; //카드 점수의 누적 합을 저장
//점수 계산을 위한 함수
int getMaxScore(vector<int>& cards, int left, int right) {
//기저 조건 : 만약 left 가 right를 넘어서면, 유효하지 않은 범위이므로 0변환
if (left > right) return 0;
//이미 계산된 값이 있으면 그 값을 사용
if (dp[left][right] != -1) return dp[left][right];
//전체 카드의 총합 중에서, 상대방이 최선의 선택을 해서 얻을 수 있는 점수를 뺀 값을 계산
int totalSum = sum[right + 1] - sum [left];
int pickLeft = totalSum - getMaxScore(cards, left+1, right);
int pickRight = totalSum - getMaxScore(cards, left, right-1);
//두 경우 중 최대값을 DP 테이블에 저장
return dp[left][right] = max(pickLeft, pickRight);
}
int main() {
int T;
cin >> T;
while(T--) {
//각 카드 점수 입력
int N;
cin >> N;
vector<int> cards(N);
for (int i=0; i<N; i++){
cin >> cards[i];
}
//DP테이블을 -1로 초기화
memset(dp, -1, sizeof(dp));
//카드 점수의 누적 합 계산
sum[0]=0;
for (int i=0; i<N; i++){
sum[i+1] = sum[i] + cards[i];
}
//0번부터 N-1번까지 카드가 있을 때 근우가 얻는 최대 점수 계산
cout << getMaxScore(cards, 0, N-1) << endl;
}
return 0;
}
2. 명우가 둘 중에 더 작은 것을 가져간다는 로직으로 짠 코드
#include <iostream>
#include <vector>
#include <cstring> //memset을 사용하기 위해
using namespace std;
int dp[1001][1001]; //dp table 정의
//점수 계산을 위한 함수
int getMaxScore(vector<int>& cards, int left, int right) {
//기저조건 : 만약 left가 right를 넘어서면, 유효하지 않은 범위이므로 0반환
if (left > right ) return 0;
//기저조건 : 만약 왼쪽=오른쪽이 같으면, 카드가 하나 남았다는 의미
if (left == right) return cards[left];
//이미 계산된 값이 있으면 그 값 사용
if (dp[left][right] != -1) return dp[left][right];
//왼쪽을 선택하는 것은 = 준우가 왼쪽 값을 얻고, 다른애가 왼쪽을 고른 것과 오른쪽을 고른 것 중 더 작은 값을 가지는 것
int pickLeft = cards[left] + min(getMaxScore(cards, left+2, right), getMaxScore(cards, left+1, right-1));
int pickRight = cards[right] + min(getMaxScore(cards, left+1, right-1), getMaxScore(cards, left, right-2));
//두 경우 중 최대값을 DP 테이블에 저장
return dp[left][right] = max(pickLeft, pickRight);
}
int main() {
int T;
cin >> T;
while (T--){
//각 카드 점수 입력
int N;
cin >> N;
vector<int> cards(N);
for (int i = 0; i<N; i++){
cin >> cards[i];
}
//DP 테이블을 -1로 초기화
memset(dp, -1, sizeof(dp));
//0번부터 N-1번까지 카드가 있을 때 근우가 얻는 최대 점수 계산
cout << getMaxScore(cards, 0, N-1) <<endl;
}
return 0;
}
프로님 설명 받아 적은 내용 ...
[DP테이블 정의하기]
dp[i][j]를 i번째 카드부터 j번째 카드까지 있을 때 근우가 얻을 수 있는 최대 점수로 정의한다.
-초기화
'i==j'인 경우, 즉 카드가 하나만 남아 있을 때, 근우는 남아있는 카드의 점수를 모두 가져갑니다. 따라서 dp[i][i]=카드의 점수[i]가 됩니다.
-점화식
근우가 왼쪽 카드i를 가져가면, 명우는 i+1부터 j까지 카드를 가져가게 됩니다.
근우가 오른쪽 카드 j를 가져가면, 명우는 i부터 j-1까지의 카드를 선택하게 됩니다.
왼쪽카드를 선택하면
a[i] + s(i,j) - d(i+1, j)
오른쪽카드
a[j] + s(i,j) - d(i, j+1)
이런 문제는 4선 DP로 쌓아올리거나, top-down or bottom-up으로 풀 수 있다.
완전탐색을 가정했을 때, 활성화됐는지 아닌지 쭉 파악하는 시간복잡도는 on/off 여부를 모두 체크하면 (2^N)이다.
입력값은 1억까지인데, 2^N으로 감당할 수 있는 입력값은 20이므로, 이는 시간복잡도 내에 해결할 수 없다.
=> 따라서 다른 방법을 생각해보자. DP로 해야한다.
DP로 하려면 3가지가 필요하다.
1)DP 배열
2)점화식
3)초깃값
dp[i][j]에서 i와 j를 뭐라고 생각할지 판단해야한다.
처음에는 i번째에 확보할 수 있는 메모리 j에 드는 비용 cost를 dp[i][j]라고 세웠다. 그런데 이렇게 되면, j의 범위가 1억까지이므로 메모이제이션이 불가능하다.
따라서 d[i][j]를 i번째 앱까지, j의 cost로 확보할 수 있는 메모리로 정의한다.
m[i]
30
10
20
35
40
비용합계
c[i]
3
0
3
5
4
15
실제로 문제 풀 때도 손으로 그려서 파악한다.
d[i][j]
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
d[1][j]
0
0
0
30
30
30
30
30
30
30
30
30
30
30
30
30
d[2][j]
10
10
10
40
40
40
40
40
40
40
40
40
40
40
40
40
d[3][j]
10
10
10
40
40
40
60
60
60
60
60
60
60
60
60
60
d[4][j]
10
10
10
40
40
45
60
60
75
75
75
95
95
95
95
95
d[5][j]
10
10
10
40
40
45
60
80
80
85
100
100
115
115
115
135
d[j-1][j-cost[i]+m[i]와 d[i-1][j] 비교
와! 점화식이 왜 d[i-1][j-cost[i]] + memory[i] 이런식인가 했더니, 해당하는 비용값을 하나 더하면 당연히 필요로 하는 j값에는 cost[i]만큼 빠진 것이 이제 필요로 되고, 대신 메모리를 얻으니까 이런거다.
강사님께서 주신 자바 코드.
package b7579;
import java.util.Scanner;
/*
* 백준 7579 앱
* updated 2024-02-17
*/
public class Main2 {
static int N, M; // 앱개수, 필요한 메모리
static int[] m; // 앱 메모리
static int[] c; // 비용
static int sum;
static int[][] d; // 비용 i번째 앱까지 j 의 비용을 들였을 때 얻을 수 있는 최대 메모리양
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
M = sc.nextInt();
m = new int[N+1];
c = new int[N+1];
for (int i=1; i<=N; i++) {
m[i] = sc.nextInt();
}
for (int i=1; i<=N; i++) {
c[i] = sc.nextInt();
sum += c[i];
}
d = new int[N+1][sum+1];
for (int i=1; i<=N; i++) {
for (int j=0; j<=sum; j++) {
if (j - c[i] >=0) d[i][j] = Math.max(d[i][j], d[i-1][j-c[i]] + m[i]);
d[i][j] = Math.max(d[i][j], d[i-1][j]);
}
}
int cost = 0;
for (int i=1; i<=N; i++)
for (cost=0; cost<sum && d[i][cost]<M; cost++);
System.out.println(cost);
}
}
그리고 C++코드로 변환한 것
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int N, M; // 앱 개수, 필요한 메모리;
cin >> N >> M;
vector<int> m(N+1); // 앱 메모리
vector<int> c(N+1); //비용
int sum = 0;
//입력 받는 것 - python과 달리, 미리 공간을 파놓고 입력을 받는다.
for (int i=1; i<=N; i++){
cin >> m[i];
}
for (int i=1; i<=N; i++){
cin >> c[i];
sum += c[i];
}
//비용 1번째 앱까지 j의 비용을 들였을 때 얻을 수 있는 최대 메모리양
vector<vector<int>> d(N+1, vector<int>(sum+1,0));
for (int i=1; i<=N; i++){
for (int j=0; j <=sum; j++){
if (j-c[i] >=0) {
d[i][j] = max(d[i][j], d[i-1][j-c[i]] + m[i]);
}
d[i][j] = max(d[i][j], d[i-1][j]);
}
}
int cost = sum;
for (int i=0; i<=sum; i++){
if (d[N][i] >= M) {
cost = i;
break;
}
}
cout << cost << endl;
return 0;
}
주어진 두 노드(시작 노드, 도착 노드)사이의 최소 비용인 '경로'를 찾을 때 유용하게 사용된다.
가중치의 값이 모두 양수일 때(즉, 가중치에 음수가 없을 때) 사용된다.
하다보면 '당장 눈 앞에 드는 비용이 더 적다고, 해당 정점을 다시는 안쳐다봐도 될까?' 이런 질문이 생길 수 있다.
하지만 가중치 값이 양수이기만 하면 이는 만족하므로 걱정하지 않아도 된다.
2. 동작 원리
2-1.O(N^2) 반복문을 통한 시간 복잡도
지금부터 '비용' 이라는 말을 많이 사용하게 될 것이다. 이 '비용' 값은 1차원 배열 Dist[] 배열에 저장되어 있다고 생각하자.
0.초기 Dist[] 배열 모든 값은, 무한대(INF)로 초기화 되어있다.
1.시작 노드와 직접적으로 연결된 모든 정점들의 거리를 비교해서 업데이트 시켜주고, 시작 노드를 방문한 노드로 체크한다.
2.방문한 정점들과 연결되어 있는 정점들 중, 비용이 가장 적게 드는 정점을 선택하고, 해당 정점을 방문한 정점으로 선택해준다.
3. 2번 과정에 의해서 갱신될 수 있는 정점들의 거리를 갱신시켜준다.
4. 2-3번 과정을 반복한다.
1. 시작 노드와 직접적으로 연결된 모든 정점들의 거리를 비교해서 업데이트 시켜주고, 시작 노드를 방문한 노드로 체크한다.
//시작 노드-연결된 i노드의 가격을 Dist 배열에 업데이트
for (int i=1; i <= V; i++) Dist[i] = MAP[Start][i];
Dist[Start]=0; //시작 노드에서의 비용은 0으로 바뀐다.
Select[Start]=true;
2.방문한 정점들과 연결되어 있는 정점들 중, 비용이 가장 적게 드는 정점을 선택하고, 해당 정점을 방문한 정점으로 선택해준다.
int Find_Shortest_Node()
{
int Min_Dist, Min_Idx;
Min_Dist = INF;
Min_Idx = -1;
for (int i=1; i<=V; i++)
{
//선택된 건 넘기자~
if (Select[i] == true) continue;
//현재 제일 작은 값보다, Dist[i] 비용이 더 작으면, Dist[i] 비용을 최솟값으로 선택
if (Dist[i] < Min_Dist)
{
Min_Dist = Dist[i];
Min_Idx = i;
}
}
return Min_Idx;
}
3. 2번 과정에 의해서 갱신될 수 있는 정점들의 거리를 갱신시켜준다.
void Update_Dist(int NewNode)
{
for (int i=1; i<=V; i++){
//선택됐던 것은 넘어가고~
if (Select[i] == true) continue;
//아닌건
if (Dist[i] > Dist[NewNode] + MAP[NewNode][i])
{
Dist[i] = Dist[NewNode] + MAP[NewNode][i];
}
}
}
4.2-3번 과정을 반복한다.
몇 번 반복해야하는지 판단해야한다.
1번 정점에서 -> 3번 정점 선택하고 업데이트 1번 2번 정점 선택 -> 업데이트 ... 6번 정점 선택 -> 종료!(업데이트 없음)
즉 업데이트는 N(정점의 수)-1번 진행한다.
[전체 코드]
//다음 노드 선택
int Find_Shortest_Node()
{
int Min_Dist, Min_Idx;
Min_Dist = INF;
Min_Idx = -1;
for (int i=1; i <=V; i++) //V는 정점의 수
{
if (Select[i] == true) continue;
if (Dist[i] < Min_Dist)
{
Min_Dist = Dist[i];
Min_Idx = i;
}
}
return Min_Idx;
}
//선택한 노드에 대해 업데이트 해주기
void Update_Dist(int NewNode)
{
for (int i=1; i <=V; i++)
{
if (Select[i] == true) continue;
if (Dist[i] > Dist[NewNode] + MAP[NewNode][i])
{
Dist[i] = Dist[NewNode] + MAP[NewNode][i];
}
}
}
void Dijkstra()
{
//시작노드 초기화
for ( int i=1; i <= V; i++) Dist[i] = MAP[Start][i];
Dist[Start] = 0;
Select[Start] = true;
//반복해줌
for (int i=0; i<V-1; i++)
{
int NewNode = Find_Shortest_Node();
Select[NewNode] = true;
Update_Dist(NewNode);
}
}
2-2. 우선순위 큐를 이용한 방법 O(E*logN) E=간선 수, N=노드 수
인접한 간선들을 모두 Queue에 집어 넣은 후, 최소힙을 구하는 연산을 통ㅎ서, 최소비용이 드는 정점부터 처리하는 방식이다.
시간복잡도가 O(E*logN)인 이유는
-모든 정점들에 대해서 최소힙으로 추출하는데 걸리는 시간복잡도가 O(logN)이다.
-각 노드마다 인접한 모든 간선들을 확인해봐야하므로 시간복잡도가 O(E)이다.
void Dijkstra_Using_Heap()
{
priority_queue<pair<int, int>> PQ; //값이 클 수록 더 높은 우선순위를 갖게된다. 따라서 아래에서 -를 붙여서 값의 크기를 반전한다.
PQ.push(make_pair(0, Start));
Dist[Start] = 0;
while (PQ.empty() ==0)
{
int Cost = -PQ.top().first; //간선 길이에 -붙이는 이유는 최소힙 구현을 위해서
int Cur = PQ.top().second;
PQ.pop();
for ( int i=0; i<Vertex[Cur].size(); i++)
{
int Next = Vertex[Cur][i].first;
int nCost = Vertex[Cur][i].second;
if (Dist[Next] > Cost + nCost)
{
Dist[Next] = Cost + nCost;
PQ.push(make_pair(-Dist[Next], Next)); //간선 길이에 -붙이는 이유는 최소힙 구현을 위해서
}
}
}
for ( int i=1; i<=V;i++)
{
if (Dist[i] == INF) cout << "INF" << endl;
else cout << Dist[i] << endl;
}
}
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int a, b, c;
cin >> a >> b;
c = a + b;
cout << c << "\n";
}
3.배운점
3-1. 띄어쓰기가 포함된 숫자 2개 입력 받기
c++에서는 다음과 같이 띄어쓰기가 포함된 연산을 입력 받을 수 있다.
cin이 자동으로 띄어쓰기를 처리해준다.
cin >> a >> b;
3-2. python의 input()처럼 한 줄로 입력받는 법
#include <iostream>
#include <string> //문자열 사용을 위해 필요하다.
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
string input_line;
getline(cin, input_line);
cout << "You entered: " << input_line <<"\n";
return 0;
}
3-2. python과의 입력 코드 및 시간 비교
python에서는 모든 입력을 문자열로 받는다. 따라서 문자열을 입력 받고 쪼갠 다음 각각의 문자에 매핑해줘야하는 코드였다.
1. c++코드 짤 때 효율적인 기본 세팅은 다음과 같습니다. #iostream #namespace #"\n"
#include <iostream>
using namespace std; // namespace 한 번에 가져오는 tip
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
std::cout << "Hello, World!" << "\n";
return 0;
}
1. C++ 3가지 입출력 정리
C++에는 기본적으로 3가지 입출력 방식이 있습니다.
3번 cstdio > 2번 stdio.h > 1번 iostream 순서로 메모리 성능이 더 좋습니다.
1.iostream : C++의 표준 입출력 헤더파일 입니다. 대표적으로 cin, cout이 있습니다.
3.cstdio : stdion.h와 크게 다를 것은 없습니다. C언어에서는 namespace 개념이 없지만, c++에서는 namespace를 지원하면서 C언어의 표준 라이브러리를 namespace안에서도 사용할 수 있도록 한 것입니다. 함수 기능상에서는 차이가 없습니다.
#include <cstdio>
main() {
int a;
scanf("%d", &a); // std::scanf도 가능
printf("%d", a); // std;:printf도 가능
}
3.예를들어, N=17일 때, 한 자릿수에서 사용할 수 있는 수는 0, 1, 2, ..., 17가지이다.
[입력]
1.첫 줄에는 A진법, B진법이 공백을 구분하여 주어진다. (2<=A,B<=30 이하의 자연수)
2.입력의 두 번째 줄에는 A진법으로 나타낸 숫자의 자리수의 개수 m(1<=m<=25)이 주어진다.
3.세 번째 줄에는 A진법을 이루고 있는 숫자 m개가 공백으로 구분되어 높은 자릿수부터 주어진다.
4.각 숫자는 0이상 A미만임이 보장되며, 0으로 시작되는 경우는 존재하지 않는다.
5.A진법 수를 10진법으로 변환하면, 양의 정수이며 2^20보다 작다.
[출력]
입력으로 주어진 A진법으로 나타낸 수를 B진법으로 변환하여 출력한다.
[예제 입력 1]
17 8
2
2 16
[예제 출력 1]
6 2
[문제 풀이]
1. a_nums를 10진수로 변환할 때, 각 자리를 변환하고 10진수로 합쳐야하는데, 다음과 같이 한 번에 처리하는 오류가 발생했다.
나는 아래 내 코드가 완벽하게, a_nums를 10진수로 바꿔준다고 생각했는데, 아닌가보다!
#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
a_nums_10=list(map(lambdax : int(x,A), a_nums))
문제에 주어져있었다. A진법을 이루고 있는 숫자 m개가 높은 자리수부터 주어진다는 것이므로 결국 하나의 숫자다.
위처럼 잘못 풀었던 첫 번째 코드
import sys
input=sys.stdin.readline
A, B = map(int,input().split())
m=int(input())
a_nums=input().split()
#1.10진수로 변환 시키는 과정 필요
###방법 : int(숫자,n진수)
#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
a_nums_10=list(map(lambda x : int(x,A), a_nums))
#2.10진수를 n진수로 변환시키는 함수 작성
def convert_to_base(num, base):
if num==0:
return 0
digits=[]
while num:
digits.append(int(num%base))
num=num//base
return ''.join(str(x) for x in digits[::-1])
#3.a_nums_10 차례대로 넣어서 반환
a_nums_b=list(map(lambda a : convert_to_base(a, B), a_nums_10))
print(*a_nums_b)
[정답코드]
import sys
input=sys.stdin.readline
A, B = map(int,input().split())
m=int(input())
a_nums = list(map(int, input().strip().split()))
#1.10진수로 변환 시키는 과정 필요
###방법 : int(숫자,n진수)
#a_nums에서 하나씩 꺼내서 x자리에 넣고, 그것은 A진수이니, 10진수로 바꿔주세요. 그리고 a_nums_10에 넣어주세요
# a_nums_10=list(map(lambda x : int(x,A), a_nums))
decimal_value=0
for num in a_nums:
decimal_value=decimal_value*A + num
#2.10진수를 n진수로 변환시키는 함수 작성
def convert_to_base(num, base):
if num==0:
return [0] #다른 return 값이 리스트이므로 [0]으로 통일
digits=[]
while num:
digits.append(int(num%base))
num=num//base
return digits[::-1]
#3.a_nums_10 차례대로 넣어서 반환
b_nums=convert_to_base(decimal_value, B)
print(' '.join(map(str,b_nums)))
1.도시를 동서로 나누는 큰 일직선 모양의 강이 흐르고 있다. 동서를 잇는 적합한 곳(사이트)에 다리를 짓고자 한다.
2.서쪽에는 N개의 사이트, 동쪽에는 M개의 사이트가 있다.(N<=M)
3.한 사이트에는 한 개의 다리만 연결 가능하며, 서쪽 사이트 개수만큼 다리를 지을 수 있는 경우의 수를 구하여라.
=>결국 오른쪽 M개의 점 중에서 N개를 선택하면, 순서대로 왼쪽에 연결된다고 생각할 수 있다.
[풀이]
지난번에 조합을 한 번 쭉 정리한 적이 있다. 하지만 암기하지는 않았었다. 그래서 기억이 나지 않는다.
정리한 노트를 보고 암기를 오늘 제대로 해야겠다.
암기하기 전에 예상되는 풀이 방법으로는,
-일반식을 새롭게 세웠었다.
-일반식을 새로 가져오는 것 외에는, 라이브러리를 사용하는 방법과, 메모이제이션 방법이 있었던 것 같다.
[정답코드]
def comb(n,k):
if k > n:
return 0
if k==0 or n==k:
return 1
#k가 n/2보다 큰 경우, nCk=nC(n-k)와 같음. 더 작은 수로 바꿔주는 과정
if k > n/2 :
k = n-k
result=1
for r in range(k): #r-1까지 돌 것임
result = result * (n-r) // (r+1)
return result
c=int(input())
for _ in range(c):
r, n = map(int, input().split())
print(comb(n,r))
[팁]
가장 큰 수인 M도 한 번 찍어봐야한다. overflow 방지를 위해서!
최대 크기가 10만으로 주어지면, nlogn 알고리즘을 사용하면 된다.
3. 순열의 순서 1722(골드5)
[문제]
1.임의의 순열(N!)은 정렬할 수 있다. 예를 들어 N=3인 경우, 6가지 경우의 수가 있다. 6가지 경우의 수를 나열하면 앞에서부터 작은 것이 순서상 앞선다.
2.N이 주어지면 , 두 가지 소문제 중 하나를 풀어야한다.
-문제 번호 1) K를 입력받고, N!에서의 K번째 순열을 구한다.
-문제 번호 2) 임의의 순열 N개를 입력받고, 이 순열이 몇 번째 순열인지를 출력하기.
4.사전 1256(골드2)
[문제]
-사전에 수록되어있는 모든 문자열은 N개의 "a"와 M개의 "z"로 이루어져있다. 다른 문자는 없고, 알파벳 순서대로 수록되어있다.
-규완이는 사전을 완성했고, 동호는 사전을 완성하지 못했다. 동호는 규완이가 자리를 비운 사이에 몰래 사전을 보려고 하기 때문에 문자열 하나만 찾을 수 있다.
-N과 M이 주어졌을 때, 규완이의 사전에서 K번째 문자열이 무엇인지 구하는 프로그램을 작성하시오.
-입력 : 첫째 줄에 세 정수 N,M,K가 주어진다.
-출력 : 규완이의 사전에서 K 번째 문자열을 출력한다. 사전에 등록된 문자열 개수가 K보다 작으면 -1을 출력한다.
[풀이]
-경우의 수만 따져가며, 앞에서부터 문자열을 채우는 것이 핵심이다.
-n,m개의 문자로 구성할 수 있는 문자열의 수는 math.comb(n+m,n)이다. a의 자리를 결정하면, 나머지를 z로 채우기만 하면 된다.
-n,m에 대해 (n-1)개의 a와, m개의 z로 만들 수 있는 문자열의 수인 math.comb(n+m-1,n-1)보다 k의 값이 크다면, 맨 앞
유독 이해가 안되는 문제...
from math import comb as c
a,b,i=map(int,input().split())
s=["z"]*(a+b)
if c(a+b,a)<i:
print(-1)
else:
ans=""
#과정을 반복하여 n,m 둘 중 하나가 0이되거나 k의 값이 0이될 때까지 반복하며 문자열을 채워나가고, 남은 문자가 있다면 그 개수만큼 뒤에 붙여준다.
while i and (a and b):
if i > c(a+b-1,a-1): #n-1개의 a와 m개의 z로 만들 수 있는 수 c(a+b-1,a-1)보다 k가 크면, 맨 앞 문자는 a이므로
ans+="z"
i-=c(a+b-1,a-1)
b-=1
else:
ans+="a"
a-=1
print(ans+"a"*a+"z"*b)
처음 중간값인 8에서 시작하고, 왼쪽과 오른쪽 중 더 높이가 높은쪽으로 확장한다. 그리고 이때의 넓이값도 적어둔다.
또 해당 직사각형과 맞닿아있는 히스토그램 중 더 높은 쪽으로 확장한다. 확장하고나서 넓이를 갱신해준다.
최종적으로 분할 기준을 포함하는 최댓값, 왼쪽 범위의 최댓값, 오른쪽 범위의 최댓값을 모두 구하고, 이 셋을 비교하여 가장 큰 값을 최댓값으로 잡아준다.
어려웠지만 코드 이해 안되는 부분은 손으로 쓰고, 직접 값 넣어서 생각하니 이해가 되었다. 까먹기 전에 복습 혹은 영상을 찍어두면 도움이 될 것 같다.
import sys
input = sys.stdin.readline
res=[]
def divide_and_conquer(histogram, start, end):
#end=start 같으면, 복잡한 과정 거칠 것 없이 그냥 histogram[end] 또는 histogram[start]다.
if end == start:
return histogram[end]
#만약 end와 start가 바로 한 칸 차이면(붙어 있으면)
elif end - start == 1:
#게다가 end보다, start가 더 높이가 높을 경우
if histogram[end] < histogram[start]:
#복잡한 과정 할 것 없이,end값에 두 배를 한 것과, start 값 중 더 큰 값 비교
return max(2*histogram[end], histogram[start])
else:
#start 값에 두 배를 한 것과, end 값 중 더 큰 값 비교
return max(2*histogram[start], histogram[end])
mid = ( start + end ) //2
left_area = divide_and_conquer(histogram, start, mid)
right_area = divide_and_conquer(histogram, mid+1, end)
left=mid-1
right=mid+1
#mid_area : 기존 넓이를 기록할 변수 초기화
mid_area = histogram[mid]
#now_height : 현재 높이를 기록할 변수 초기화. 시작은 중간값
now_height = histogram[mid]
while start <= left and right <= end:
#왼쪽과 오른쪽 중 오른쪽 히스토그램 높이가 더 높으면
if histogram[left] < histogram[right]:
#'오른쪽 히스토그램 높이' 보다, '현재 높이'가 더 높으므로
if histogram[right] < now_height:
#'현재 높이'를 더 낮은 '오른쪽 히스토그램 높이'로 설정한다.
now_height = histogram[right]
#그리고 현재까지의 넓이(mid_area)와, 새롭게 지정된 높이(now_height)에 대한 넓이를 비교하여
#더 높은 것을 새로운 mid_area로 지정한다.
mid_area = max(mid_area, now_height * (right-left))
#그리고 오른쪽으로 한 칸 범위를 높인다.
right +=1
#왼쪽과 오른쪽 중 왼쪽 히스토그램 높이가 더 높으면
else:
if histogram[left] < now_height:
now_height = histogram[left]
mid_area = max(mid_area, now_height*(right - left))
left -= 1
#right는 범위를 벗어났고, 조건이 없어졌다. 왼쪽으로 확장할 공간만 남은 상황.
while start <= left:
if histogram[left] < now_height:
now_height = histogram[left]
mid_area = max(mid_area, now_height * ( right - left ))
left -=1
while right <= end:
if histogram[right] < now_height:
now_height = histogram[right]
mid_area = max(mid_area, now_height * ( right - left))
right+=1
return max(left_area, right_area, mid_area)
res=[]
while True:
#직사각형의 수, 직사각형의 높이들 주어짐 ex) 7 2 1 4 5 1 3 3
histogram = list(map(int, input().split()))
n = histogram[0]
#입력 마지막 시에 종료 조건
if n ==0:
break
#1부터 넘겨주어, 0번째인 값은 해당되지 않도록 함.
res.append(divide_and_conquer(histogram, 1, n))
for i in res:
print(i)
6. 가장 긴 증가하는 부분 수열 11053
[문제]
가장 긴 증가하는 부분 수열의 길이를 구하는 문제.
예를 들어 [10, 20, 10, 30, 20, 50]이 주어지면,
[10, 20, 10, 30, 20, 50]
[팁]
-최대수가 1,000으로 주어지면, n^2으로 해결할 수 있다.
-최장증가부분수열(LIS)
-앞에서부터 하나씩 확인한다.
-나보다 더 앞에 작은게 있어야 붙을 수 있음.
-원리를 계산해봐도 n^2 알고리즘이라는 것을 파악할 수 있음.
[풀이]
dp[i] = A[i]를 마지막 원소로 가지는 부분 수열의 최대 길이.
=> A[i]가 어떤 증가부분수열의 마지막 값이 되기 위해서는,
A[i]가 추가되기 전 증가부분수열의 마지막 값이, A[i]보다 작은 값이어야한다.
A[i]를 마지막 값으로 가지는 '가장 긴' 증가부분수열의 길이는,
A[i]가 추가될 수 있는 증가부분수열 중 가장 긴 수열의 길이에 1을 더한 값이 된다.
n=int(input())
arr = list(map(int, input().split()))
#i가 0일 때 증가하는 최대 부분 수열의 길이는 1이기 때문에, 테이블을 우선 전부 1로 초기화해줌
d = [1]*n
for i in range(1, n): #1부터 n-1번 인덱스까지의 모든 i에 대하여
for j in range(i): #i보다 작은 j (0부터 i-1까지 = i보다 작은 것 모두)
if arr[j] < arr[i]: #j의 원소가 i의 원소보다 작은 값이 존재하면!, 즉, 부분적으로 증가한다면
d[i] = max(d[i], d[j]+1) #i에서의 최적의 해를 갱신해준다.
print(max(d))
문제 원리는 이해가 됐는데, 코드 이해가 덜 되었다. 따로 이해 필요하다. 유튜브에 강의가 존재하므로 확인해봐야겠다.