문제

 

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

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

www.acmicpc.net

풀이 전략

  • for문을 사용하여, 현재 인덱스 앞에서 자기보다 큰, 방문할 수 있는 위치를 확인하여 진행한다.
    • 20 20 50 10 70 -> 20에서 50을 방문할 경우, 70 방문하는 경우로 재귀 함수 호출
      • 20 20 50 10 70 -> 70을 새로 방문 가능
      • 20 20 50 10 70 -> 더 방문할 곳이 없으므로 종료됨
  • 해당 index를 방문할 때의 길이를 기록하여, 더 작은 부분수열로 방문하지 않도록 한다
    • A : 10 20 30 50 70  / i=4의 Length = 5 기록
    • B : 10 20 30 50 70  / i=4의 Length = 3 : 기록된 Length = 5보다 작으므로 방문하지 않음
  • 일 때, 방문시 최대 길이를 기록해 둘 경우 Length = 5 에서만 방문하면 된다.
  • 재귀함수로 구현하고, for loop를 사용한 호출이 끝난 후 최대 길이를 확인하여 기록한다.
void run(int idx, int current, int length)
{
	for (int i = idx; i < N; i++)
	{
		if (current < A[i] && visitLength[i] < length+1)
		{
			visitLength[i] = length + 1;
			run(i, A[i], length + 1);
		}
	}
	if (maxLength < length)
	{
		maxLength = length;
	}

}

전체 소스 코드

더보기
더보기
//Backjoon Problem No.11053
//https://www.acmicpc.net/problem/11053
//Mist16, 2022-03-01

#include <iostream>

using namespace std;

int A[1000]= { 0, };
int visitLength[1000] = { 0, };
int N = 0;

int maxLength = 0;

void run(int idx, int current, int length)
{
	for (int i = idx; i < N; i++)
	{
		if (current < A[i] && visitLength[i] < length+1)
		{
			visitLength[i] = length + 1;
			run(i, A[i], length + 1);
		}
	}
	if (maxLength < length)
	{
		maxLength = length;
	}

}

int main()
{
	//입출력 속도 증가
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> N;

	for (int i = 0; i < N; i++)
	{
		cin >> A[i];
	}
	run(0, 0, 0);
	cout << maxLength;

	return 0;
}

Note

  • 조건을 검사하지 않고, 방문하지 않을 경우에도 재귀함수를 호출할 경우에는 O(n!)가 되어 시간 초과로 실패하는 문제였다.

'Problem Solving > Backjoon' 카테고리의 다른 글

[백준][C++]1043 - 거짓말  (0) 2022.03.03
[백준][C++]9663 - N-Queen  (0) 2022.03.02
[백준][C++]10830 - 행렬 제곱  (0) 2022.02.05
[백준][C++]1007 - 벡터 매칭  (0) 2022.01.25
[백준][C++]17070 - 파이프 옮기기 1  (0) 2022.01.22

기존에는 레벨 3 따둔걸로 만족하고있다가, 이번에 레벨 4도 따보자 싶어서 별다른 준비없이 도전해봤는데,

효율성 문제로 50점에서 마무리되었다.

1번 문제의 경우도 테스트 케이스 실패는 없었지만, 시간제한 초과로 인해서 대부분 감점이 일어났다.

 

당장 문제를 해결하는것도 좋지만, 어떻게하면 더 빠르게 해결할지도 고민해봐야 할 듯.

'잡담' 카테고리의 다른 글

백준 골드 3 달성  (0) 2022.04.01
백준 골드 5 달성  (0) 2022.02.10

백준 골드 5 달성.

100문제 남짓을 풀면서, 굳어있던 알고리즘 머리가 조금 말랑해지기는 한 것 같다.

 

앞으로도 실버 상위~골드 문제 위주로, 하루 한두문제는 꾸준히 풀어가자.

다음 목표는 골드3.

'잡담' 카테고리의 다른 글

백준 골드 3 달성  (0) 2022.04.01
프로그래머스 레벨 4 첫 시도, 절반은 했다.  (0) 2022.02.27

문제

 

10830번: 행렬 제곱

크기가 N*N인 행렬 A가 주어진다. 이때, A의 B제곱을 구하는 프로그램을 작성하시오. 수가 매우 커질 수 있으니, A^B의 각 원소를 1,000으로 나눈 나머지를 출력한다.

www.acmicpc.net

 

풀이 전략

  • A^n = A^(n/2) * A^(n/2) 인 점을 이용하여, 분할 정복한다.
  • 분할 정복으로 곱셈 문제를 해결할 경우 중복된 계산이 많이 발생하므로, 제곱 계산의 결과를 메모이제이션하여 중복된 계산을 생략한다.
  • 이 때, N이 최대 100,000,000,000이고 분할당 1/2으로 감소하므로, Array 대신 Map을 사용하여 결과를 저장하도록 한다.

전체 소스 코드

더보기
//Backjoon Problem No.10830
//https://www.acmicpc.net/problem/10830
//Mist16, 2022-02-05
#include <iostream>
#include <map>

using namespace std;

unsigned int N = 0;
unsigned long long B = 0;

struct matrix
{
	unsigned int mat[5][5];

	matrix()
	{
		for (int i = 0; i < 5; i++)
			for (int j = 0; j < 5; j++)
				mat[i][j] = 0;
	}
};

map<unsigned long long, matrix> dices;

matrix matrixMulti(const matrix& a, const matrix& b)
{
	matrix temp;

	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			unsigned int tempv = 0;
			for (int k = 0; k < N; k++)
			{
				tempv = tempv + (a.mat[i][k] * b.mat[k][j]);
			}
			tempv = tempv % 1000;
			temp.mat[i][j] = tempv;

		}
	}
	return temp;
}

matrix getNSquare(unsigned long long n)
{
	auto iter = dices.find(n);

	if (iter == dices.end())
	{
		unsigned long long a, b;
		a = n / 2;
		b = n - a;
		matrix tempmat;
		tempmat = matrixMulti(getNSquare(a), getNSquare(b));
		dices.insert(pair <unsigned long long, matrix>(n, tempmat));
		return tempmat;

	}
	return iter->second;
}

void run()
{
	matrix result = getNSquare(B);
	
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			cout << result.mat[i][j] % 1000 << " ";
		}
		cout << endl;
	}
	return;
}

int main()
{
	//입출력 속도 증가
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	matrix temp;
	
	cin >> N >> B;

	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			cin >> temp.mat[i][j];
		}
	}

	dices.clear();
	dices.insert(pair <unsigned long long, matrix>(1, temp));

	run();

	return 0;
}

풀이 중 주의 사항

  • N의 범위가 0 ~ 100,000,000,000이므로, 충분히 큰 변수 형태를 사용하여 저장한다.
  • 행렬의 구성요소가 1000으로 입력되고, 제곱연산을 하지 않을 경우에도 0으로 출력해야 하는 점을 주의하도록 한다.

문제

 

1007번: 벡터 매칭

평면 상에 N개의 점이 찍혀있고, 그 점을 집합 P라고 하자. 집합 P의 벡터 매칭은 벡터의 집합인데, 모든 벡터는 집합 P의 한 점에서 시작해서, 또 다른 점에서 끝나는 벡터의 집합이다. 또, P에 속

www.acmicpc.net

 

풀이 전략

  • 평면상의 점들을 두개씩 짝지어 벡터를 만들고, 그 벡터들의 합을 구한 후 길이로 변환한다.
  • 다만, 브루트포스로 for문을 돌릴 경우 N! 만큼의 계산이 필요하여 시간제한에 맞출 수 없다.
  • V1 + V2 + V3 + V4 + .... = (a1-b1) + (a2 - b2) + ... = (a1 + b1 - b1 - b1).... 이므로
    • 벡터들의 합 = (전체 좌표의 합) - 2 * (점 절반의 합)이 된다.
  • 해당 방법을 사용하여, N개의 점 중 N/2개의 점을 도출해낸 뒤, 벡터 길이의 최소값을 구하는 방법으로 풀이한다.
    • for문 대신 재귀함수를 사용하여 현재 인덱스 / 선택해야 하는 점의 개수를 인자로 사용하는 방식으로 구현하였다. N의 최대값이 20이었으니 비트마스킹을 사용해도 괜찮았을 듯.
	void pick(int left, int currentIdx, long long vectorX, long long vectorY)
	{
		if (left == 0)
		{
			long long X = totalX - vectorX - vectorX;
			long long Y = totalY - vectorY - vectorY;
			unsigned long long temp = (X * X) + (Y * Y);
			if (temp < MinValue)
			{
				MinValue = temp;
			}
			return;
		}
		if (currentIdx >= N)
			return;
		pick(left, currentIdx + 1, vectorX, vectorY);
		pick(left - 1, currentIdx + 1, vectorX + pos[currentIdx][0], vectorY + pos[currentIdx][1]);
	}

 

전체 소스 코드

더보기
//Backjoon Problem No.1007
//https://www.acmicpc.net/problem/1007
//Mist16, 2022-01-24
#include <iostream>
#include <limits>
#include <math.h>

using namespace std;

class VectorMatching
{
private:
	int N;
	int pos[20][2];
	long long totalX;
	long long totalY;
	unsigned long long MinValue;
public:
	VectorMatching()
	{
		N = 0;
		MinValue = 0xffffffffffffffff;
		totalX = 0;
		totalY = 0;
	}

	void input()
	{
		cin >> N;
		for (int i = 0; i < N; i++)
		{
			cin >> pos[i][0] >> pos[i][1];
			totalX += pos[i][0];
			totalY += pos[i][1];
		}
	}

	void Run()
	{
		pick(N / 2, 0, 0, 0);
		long double temp = sqrtl(MinValue);
		cout.precision(16);
		cout << temp << endl;
	}

	void pick(int left, int currentIdx, long long vectorX, long long vectorY)
	{
		if (left == 0)
		{
			long long X = totalX - vectorX - vectorX;
			long long Y = totalY - vectorY - vectorY;
			unsigned long long temp = (X * X) + (Y * Y);
			if (temp < MinValue)
			{
				MinValue = temp;
			}
			return;
		}
		if (currentIdx >= N)
			return;
		pick(left, currentIdx + 1, vectorX, vectorY);
		pick(left - 1, currentIdx + 1, vectorX + pos[currentIdx][0], vectorY + pos[currentIdx][1]);
	}

};

int main()
{
	//입출력 속도 증가
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int testCases;

	//input n, pipemap;
	cin >> testCases;
	for (int i = 0; i < testCases; i++)
	{
		VectorMatching vMatch;
		vMatch.input();
		vMatch.Run();
	}
	return 0;
}

여담

  • 수학적으로 계산을 정리함으로서 계산 단계를 줄일 수 있는 문제였다. 바로 코딩하기보다는, 식을 확인하고 한 번 정리한 뒤 풀이에 들어가는 것이 좋을 듯 하다.
  • n개의 수 중 m개를 선택하는 상황에서, for문으로 n회를 돌리는 대신, i번째 수를 넣을지/안 넣을지로 재귀 함수를 사용하는 방법도 괜찮은 것 같다.

문제

 

17070번: 파이프 옮기기 1

유현이가 새 집으로 이사했다. 새 집의 크기는 N×N의 격자판으로 나타낼 수 있고, 1×1크기의 정사각형 칸으로 나누어져 있다. 각각의 칸은 (r, c)로 나타낼 수 있다. 여기서 r은 행의 번호, c는 열의

www.acmicpc.net

 

풀이 전략

  • 파이프는 좌, 하단, 좌하단대각선으로만 움직인다.
    • 파이프라인이 재귀될 염려가 없으므로, 파이프 이동선을 기억할 필요는 없다.
  • 모든 경우의 수를 탐색하여, 가능한 경우의 수를 합산한다.
//파이프 설치
int installPipe(int y, int x, int cond)
{
	int AllCases = 0;

	if (x == N-1 && y == N-1)
		return 1;

	if ((checkMap[y][x] & pipeCond::ConditionChecked) != pipeCond::ConditionChecked ) //unchecked, check
	{
		checkConstructProb(y, x);
	}
	//1. Left/LDown -> Left
	if (cond == pipeCond::Left || cond == pipeCond::LDown)
	{
		if((checkMap[y][x] & pipeCond::Left) == pipeCond::Left)
		{
			AllCases += installPipe(y, x + 1, pipeCond::Left);
		}
		
	}
	//2. Down/LDown -> Down
	if (cond == pipeCond::Down || cond == pipeCond::LDown)
	{
		if ((checkMap[y][x] & pipeCond::Down) == pipeCond::Down)
		{
			AllCases += installPipe(y + 1, x, pipeCond::Down);
		}
	}
	//3. All Cond -> LDown
	if ((checkMap[y][x] & pipeCond::LDown) == pipeCond::LDown)
	{
		AllCases += installPipe(y + 1, x + 1, pipeCond::LDown);
	}

	return AllCases;
}
  • 이 때, 같은 위치의 파이프 설치여부를 여러번 체크하게 되므로, 다이나믹 프로그래밍으로 풀이한다.
    • enum을 사용하여 각 상태를 정의해둔 후, 비트연산자 배열에 저장한다.
    • 타설가능 파이프 외에 검사했는지 여부를 확인하게 하여, 타설불가능 지역을 계속해서 체크하는 것을 방지한다.
int checkMap[16][16] = { 0, };
enum pipeCond
{
	None = 0,
	Left = 1,
	LDown = 2,
	Down = 4,
	ConditionChecked = 8
};
//현재 위치에서 파이프 타설이 가능한지 체크
void checkConstructProb(int y, int x)
{
	int cond = pipeCond::ConditionChecked;
	//1. Left
	if (x + 1 < N)
	{
		if (pipeMap[y][x + 1] == 0)
			cond = cond | pipeCond::Left;
	}
	//2. Down
	if (y + 1 < N)
	{
		if (pipeMap[y + 1][x] == 0)
			cond = cond | pipeCond::Down;
	}
	//3. Left-Down
	if ((cond & pipeCond::Left) == (int)pipeCond::Left &&
		(cond & pipeCond::Down) == (int)pipeCond::Down)
	{
		if (pipeMap[y + 1][x + 1] == 0)
		{
			cond = cond | (int)pipeCond::LDown;
		}
	}
	checkMap[y][x] = cond;
}

 

전체 소스 코드

더보기
더보기
//Backjoon Problem No.17070
//Mist16, 2022-01-22

#include <iostream>

using namespace std;

enum pipeCond
{
	None = 0,
	Left = 1,
	LDown = 2,
	Down = 4,
	ConditionChecked = 8
};

int pipeMap[16][16] = { 0, };
int checkMap[16][16] = { 0, };
int N = 0;



//현재 위치에서 파이프 타설이 가능한지 체크
void checkConstructProb(int y, int x)
{
	int cond = pipeCond::ConditionChecked;
	//1. Left
	if (x + 1 < N)
	{
		if (pipeMap[y][x + 1] == 0)
			cond = cond | pipeCond::Left;
	}
	//2. Down
	if (y + 1 < N)
	{
		if (pipeMap[y + 1][x] == 0)
			cond = cond | pipeCond::Down;
	}
	//3. Left-Down
	if ((cond & pipeCond::Left) == (int)pipeCond::Left &&
		(cond & pipeCond::Down) == (int)pipeCond::Down)
	{
		if (pipeMap[y + 1][x + 1] == 0)
		{
			cond = cond | (int)pipeCond::LDown;
		}
	}
	checkMap[y][x] = cond;
}

//파이프 설치
int installPipe(int y, int x, int cond)
{
	int AllCases = 0;

	if (x == N-1 && y == N-1)
		return 1;

	if ((checkMap[y][x] & pipeCond::ConditionChecked) != pipeCond::ConditionChecked ) //unchecked, check
	{
		checkConstructProb(y, x);
	}
	//1. Left/LDown -> Left
	if (cond == pipeCond::Left || cond == pipeCond::LDown)
	{
		if((checkMap[y][x] & pipeCond::Left) == pipeCond::Left)
		{
			AllCases += installPipe(y, x + 1, pipeCond::Left);
		}
		
	}
	//2. Down/LDown -> Down
	if (cond == pipeCond::Down || cond == pipeCond::LDown)
	{
		if ((checkMap[y][x] & pipeCond::Down) == pipeCond::Down)
		{
			AllCases += installPipe(y + 1, x, pipeCond::Down);
		}
	}
	//3. All Cond -> LDown
	if ((checkMap[y][x] & pipeCond::LDown) == pipeCond::LDown)
	{
		AllCases += installPipe(y + 1, x + 1, pipeCond::LDown);
	}

	return AllCases;
}

int main()
{
	//입출력 속도 증가
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	//input n, pipemap;
	cin >> N;
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N; j++)
		{
			cin >> pipeMap[i][j];
		}
	}
	cout << installPipe(0, 1, pipeCond::Left);

	return 0;
}

 

여담

  • 골드 하위권 문제까지는 빠르게 풀 수 있는 거 같다. 고난이도 문제에 도전해볼 것.

문제

  • 1202번 : 보석 도둑
  • Rank : Gold II
  • 링크
 

1202번: 보석 도둑

첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000) 다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000) 다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci

www.acmicpc.net

풀이 전략

  • 보석의 정보를 담은 구조체를 만들어 list에 입력받은 뒤, price 기준으로 정렬하여 비싼 보석부터 확인하였다.
    • bool operator<(const Jewel s) const를 구현하여 정렬
  • 가방의 경우 multiset에 저장한 뒤, lower_bound를 사용하여 비싼 보석부터 하나씩 매칭.
  • 보석을 전부 확인하거나, 가방이 가득 찬 경우 종료.

소스 코드

//Backjoon Problem No.1202
//Mist16, 2022-01-21

#include <iostream>
#include <list>
#include <set>
using namespace std;

struct Jewel
{
	int weight;
	int price;
	Jewel(int wei, int pri) : weight(wei), price(pri) {}

	//정렬시 price 기준으로 소트되도록 설정
	bool operator<(const Jewel s) const
	{
		return this->price > s.price;
	}
};

list<Jewel> jewels;
multiset<int> Bags;

int N, K;

int main()
{
	int tempWeight, tempValue;
	long long int totalPrice = 0;
	//입출력 속도 증가
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> N >> K;
	for (int i = 0; i < N; i++)
	{
		cin >> tempWeight >> tempValue;
		jewels.push_back(Jewel(tempWeight, tempValue));
	}
	for (int i = 0; i < K; i++)
	{
		cin >> tempWeight;
		Bags.insert(tempWeight);
	}
	jewels.sort();

	//가장 비싼 보석부터, 가능한한 가장 작은 가방에 적재
	for (auto iter1 = jewels.begin(); iter1 != jewels.end(); iter1++)
	{
		auto iter2 = Bags.lower_bound(iter1->weight);
		if (iter2 != Bags.end())
		{
			totalPrice += iter1->price;
			Bags.erase(iter2);
			//모든 가방이 찼을 경우 종료
			if (Bags.size() == 0)
			{
				cout << totalPrice;
				return 0;
			}
		}
	}
	cout << totalPrice;
	return 0;
}

 

여담

  • 10만 개 이상의, 대량의 데이터를 가공하는 문제에서 자료구조 선택으로 인한 시간 문제가 발생하는 경우가 있다.
    • 각 자료구조별 순차접근/임의접근, 삽입/삭제 효율에 관해 정리해보는것도 좋을 듯.

문제

  • 16566번 : 카드 게임
  • Rank : Emerald V
  • 링크
 

16566번: 카드 게임

첫째 줄에 세 개의 자연수 N, M, K가 주어진다. (1 ≤ M ≤ N ≤ 4,000,000, 1 ≤ K ≤ min(M, 10,000)) 다음 줄에 카드의 번호를 나타내는 M개의 자연수가 주어진다. 각각의 수들은 1 이상이고 N 이하이며 서로

www.acmicpc.net

 

풀이 전략

  • STL::Vector을 사용하여 한번에 입력받은 후 정렬 실행
    • 입력 이전에 reserve 공간 확보(대량의 move 실행 방지)
  • 카드 입력을 비교하여 상대방의 카드가 MAX 이상, MIN 이하일 경우, 최저 카드를 제출
  • 중간값일 경우 이진 검색을 실행하여 해당 카드를 이길 수 있는 카드를 찾아 제출
  • 제출 후, 뽑은 카드는 Vector에서 삭제

결과

  • 공개Test case, 자체 Test Case는 성공하나, 제출 진행시 시간부족으로 실패 처리
  • 백준 테스트 환경에서 대형 케이스(카드 4백만, 뽑을 카드 1만)를 시도할 경우, 시간부족으로 실패하는 것으로 보임.
  • Vector 대신 자체 이진 그래프를 작성하여 시도해볼 것

 

소스 코드

더보기
//Backjoon Problem No.16566
//Mist16, 2022-01-21

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


class cardDeck
{
private:
	vector<int> cardSet;

public:

	void setSize(int num)
	{
		cardSet.reserve(num);
	}
	void insertCard(int num)
	{
		cardSet.push_back(num);
	}

	void sortCards()
	{
		sort(cardSet.begin(), cardSet.end());
	}
    
	void deleteCard(vector<int>::iterator iter)
	{
		cardSet.erase(iter);
	}

	vector<int>::iterator getMinCard()
	{
		auto iter = cardSet.begin();
		return iter;
	}
	vector<int>::iterator getMaxCard()
	{
		auto iter = cardSet.end();
		iter--;
		return iter;
	}

	int popCard(int target)
	{
		vector<int>::iterator popOutCard;
		int outCardNum;
		if (target >= *getMaxCard() || target <= *getMinCard()) //losing hand
		{
			popOutCard = getMinCard();
			outCardNum = *popOutCard;
			deleteCard(popOutCard);
			return outCardNum;
		}
		else
		{
			popOutCard = findCard(target);
			outCardNum = *popOutCard;
			deleteCard(popOutCard);
			return outCardNum;
		}
	}

	vector<int>::iterator findCard(int target)
	{
		auto start = cardSet.begin();
		auto end = cardSet.end() - 1;
		int size = cardSet.size();

		while (1)
		{
			auto middle = start + (size / 2);
			if (size == 1)
			{
				return start;
			}
			size = size - (size / 2);

			if (*middle == target)
			{
				return middle + 1;
			}
			else if (*middle < target)
			{
				start = middle;
				continue;
			}
			else
			{
				end = middle;
				continue;
			}
		}
	}

};

int N, M, K;
cardDeck myDeck;

int main()
{
	int cardNum = 0;

	//입출력 속도 관리용
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	cin >> N >> M >> K;

	//reserve 함수를 사용하여, 미리 vector의 공간을 확보해둔다
	myDeck.setSize(M);

	for (int i = 0; i < M; i++)
	{
		cin >> cardNum;
		myDeck.insertCard(cardNum + 1);
	}
	myDeck.sortCards();

	for (int i = 0; i < K; i++)
	{
		cin >> cardNum;
		cout << myDeck.popCard(cardNum) << "\n";
	}
}

 

+ Recent posts