반응형
문제 설명
 

algospot.com :: PASS486

비밀번호 486 문제 정보 문제 재훈이는 한 번 담배를 끊겠다고 다짐할 때마다 이메일 계정 비밀번호를 바꾸는 습관이 있습니다. 재훈이는 비밀번호를 항상 "no-smok**X**" 와 같이 정하는데, 여기서 X

www.algospot.com

  • n개의 약수를 갖는 숫자 X를 비밀번호로 선택한다.(n < 400)
  • 비밀번호는 lo~hi 범위 내의 숫자이다(양 끝의 수 포함)(1 <= lo <= hi <= 10,000,000)

=> lo ~ hi 범위 이내에 정확히 n개의 약수를 갖는 숫자의 개수를 출력하기

 

문제 풀이

약수의 개수 미리 구해두기

이 문제는 어떤 한 수가 가지는 약수의 개수를 구해야 한다.

그렇기 때문에 약수 개수를 미리 배열에 구해두고, 나중에 사용하면 한 숫자에 대해 두 번 계산하는 경우를 방지할 수 있다.


a가 b의 약수라는 것은 곧 b는 a의 배수라는 뜻이다.

그러므로 a의 배수인 모든 수에 약수의 개수를 +1 해주고, a+1의 배수, a+2의 배수, ... 를 거쳐가면서 약수의 개수를 전부 계산하면, 1 ~ 10,000,000의 모든 약수의 개수를 구할 수 있다.

 

그 후 lo와 hi사이에 포함되는 수 중 n개의 약수를 가진 숫자의 개수를 셌다.

 

문제 풀이 코드
#include <iostream>

#define MAXVALUE 10000000

using namespace std;

int divisorCnt[MAXVALUE + 1];	// 약수 개수 저장

void calDivisorCnt() {	// 약수 배열 만들기
	for (int i = 2; i <= MAXVALUE; ++i) {
		for (int j = i; j <= MAXVALUE; j += i) {
			++divisorCnt[j];
		}
	}
}

int main() {
	fill_n(divisorCnt, MAXVALUE + 1, 1);
	calDivisorCnt();
	int C;
	cin >> C;
	for (int tc = 1; tc <= C; ++tc) {
		int n, lo, hi;
		int ans = 0;
		cin >> n >> lo >> hi;
		for (int i = lo; i <= hi; ++i) {
			if (divisorCnt[i] == n)
				++ans;
		}
		cout << ans << endl;
	}
	return 0;
}

 

 

반응형

'알고리즘 문제풀이 > 알고리즘 문제해결전략' 카테고리의 다른 글

16.4_졸업 학기_GRADUATION  (0) 2023.04.15
14.6_마법의 약_POTION  (0) 2023.04.09
12.4_캐나다 여행_CANADATRIP  (0) 2023.04.06
13.3_승률올리기_RATIO  (0) 2023.04.04
11.7_카쿠로_KAKURO2  (2) 2023.03.26
반응형
문제 설명
 

algospot.com :: CANADATRIP

캐나다 여행 문제 정보 문제 동건이는 여름 방학을 맞아 자동차를 끌고 캐나다 횡단 여행을 떠나기로 했습니다. 캐나다의 1번 고속도로는 세계에서 가장 긴 고속도로 중 하나로, 캐나다의 동쪽

www.algospot.com

  • N개의 도시가 주어진다.(1 <=  N <= 5000)
  • 각 도시의 위치를 L, 도시까지의 남은 거리를 표지판으로 M미터 전부터 G미터 간격마다 표시한다.(1 <= G <= M <= L = 8,030,000)
  • M은 항상 G의 배수이다.
  • K값이 주어진다.(1 <= K <= 2^31-1)

=> K번째 표지판의 위치를 출력하기

 

문제 풀이

결정문제로 바꾸기

K번째 표지판을 찾기에는 표지판의 위치가 도시마다 제각각이다.

그래서 "K번째 표지판의 위치 찾기" -> "어떤 dist 거리 까지의 표지판의 개수가 K보다 클까? 작을까?" 라는 결정문제로 바꾸어 생각했다.

 

각 도시가 가지는 정보

각 도시의 정보로 구할 수 있는 값은 다음과 같다.

  • L : 도시의 위치
  • M : 표지판이 시작되는 도시기준 상대위치
  • G : 표지판이 배치되는 간격
  • S : 표지판이 시작되는 절대 위치
  • C : 표지판의 개수

L, M, G는 문제에서 주어지지만, S는 L-M으로, C 는 M / G + 1으로 구할 수 있다.

 

특정거리까지의 표지판 개수 구하기

특정 거리가 dist라면 각 도시와 dist거리까지의 관계는 다음 세 가지의 경우로 발생한다.

  1. 도시 위치가 dist보다 앞에 있는 경우(L <= dist)
  2. 도시의 표지판이 시작되는 지점과 도시의 위치 사이에 있는 경우(S <= dist < L)
  3. 도시의 표지판이 시작되는 지점보다 dist가 앞에 있는 경우(dist < S)

1번의 경우라면 해당 도시의 표지판 개수(C)가 전부 세어질 것이다.

2번의 경우라면 dist부터 표지판이 시작되는 지점(S) 사이에 있는 표지판의 개수가 세어질 것이다.

3번의 경우라면 표지판이 세어지지 않는다.

 

이분 탐색

이분탐색으로 시간을 단축시킬 수 있다.

dist 기준으로 세어진 표지판의 개수가 K보다 크다면 더 작은 dist값으로,

dist 기준으로 세어진 표지판의 개수가 K보다 작다면 더 큰 dist값으로

바꾸어가면서 K개가 세어지는 dist값을 구하면 된다.

 

문제 풀이 코드
#include <iostream>

#define MAX(a,b)(a > b ? a : b)
#define MIN(a,b)(a < b ? a : b)

using namespace std;

struct City {
	int L, M, G, S, C;	// L, M, G, Start, Count
};

int N, K;
City cities[5000];

int countSign(int dist) {	// dist 까지의 표지판 개수 세기
	int count = 0;
	for (int i = 0; i < N; ++i) {
		if (cities[i].L <= dist) {	// 도시의 위치가 dist보다 앞에 있다면
			count += cities[i].C;
		}
		else if (cities[i].S <= dist) {	// 표지판의 시작점이 dist보다 앞에 있다면
			count += (dist - cities[i].S) / cities[i].G + 1;
		}
	}
	return count;
}

int solve(int startDist, int endDist) {	// 이분탐색
	if (startDist >= endDist) {
		return startDist;
	}
	int min = (startDist + endDist) / 2;
	int count = countSign(min);
	if (count >= K) return solve(startDist, min);
	return solve(min + 1, endDist);
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int T;
	cin >> T;
	for (int tc = 1; tc <= T; ++tc) {
		cin >> N >> K;
		int startDist = 8030000;
		int endDist = 1;
		for (int i = 0; i < N; ++i) {
			cin >> cities[i].L >> cities[i].M >> cities[i].G;
			cities[i].S = cities[i].L - cities[i].M;		// 표지판 시작 위치
			cities[i].C = cities[i].M / cities[i].G + 1;	// 표지판의 개수
			startDist = MIN(cities[i].S, startDist);
			endDist = MAX(cities[i].L, endDist);
		}
		cout << solve(startDist, endDist) << endl;
	}
}

 

 

반응형

'알고리즘 문제풀이 > 알고리즘 문제해결전략' 카테고리의 다른 글

14.6_마법의 약_POTION  (0) 2023.04.09
14.3_비밀번호486_PASS486  (0) 2023.04.09
13.3_승률올리기_RATIO  (0) 2023.04.04
11.7_카쿠로_KAKURO2  (2) 2023.03.26
12.2_남극 기지_ARCTIC  (0) 2023.03.21
반응형
문제 설명

 

 

algospot.com :: RATIO

승률올리기 문제 정보 문제 싸비는 윈도우XP 운영체제에 포함되어 있는 스파이더 카드게임을 매우 좋아한다. 처음에는 지는 경우가 있었는데, 점점 연습을 함에 따라 필승법을 발견하였고 매번

www.algospot.com

  • 플레이 횟수 N (1 <= N <= 1,000,000,000)
  • 승리 횟수 M (0 <= M <= N)
  • 승률 Z ( M / N , 단 소수점 버림)

=> N과 M이 주어질 때, 승률 Z를 1 이상 올리기 위해서 몇 번 더 이겨야 하는지 출력하기

 

문제 풀이

수식으로 바꾸기

플레이 횟수와 승리 횟수에 따른 승률 Z는 다음과 같다.

그리고, x번 플레이를 해서 x번 이겼을 때(연승했을 때), Z가 1 이상 올라가려면 다음과 같은 식을 성립해야 한다.

식을 정리하면 최종적으로 아래와 같다.

 

소수점에 따른 횟수 변화

위의 최종 식에서 좌변은 정수이고 우변은 실수값이다.

우변의 계산 식 결과값이 소수점 값을 가지고 있다면, 좌변의 x는 우변을 올림한(+1) 값이어야 식을 성립한다.

따라서, 우변 값에 따라 x 또는 x+1이 값이 될 것이다.

 

문제 풀이 코드

 

#include <iostream>

using namespace std;

long long N, M, Z;

int main() {
	int T;
	cin >> T;
	for (int tc = 1; tc <= T; ++tc) {
		cin >> N >> M;
		Z = (M * 100LL) / N;	// 비율 계산
		if (Z >= 99) {			// 99 이상이면 구하지 못함
			cout << -1 << endl;
			continue;
		}
		int x = ((Z + 1LL) * N - 100LL * M) / (99LL - Z);	// 횟수 계산
		int newZ = (((M + x) * 100LL) / (N + x));			// 새로운 비율
		if (newZ > Z) {
			cout << x << endl;
		}
		else {
			cout << x + 1 << endl;
		}
	}
	return 0;
}

 

반응형
반응형
문제 설명
 

algospot.com :: KAKURO2

Kakuro II 문제 정보 문제 Having written a input file generator in Kakuro I, we can now set problems to solve Kakuro puzzles. Write a program to solve kakuro puzzles. For Kakuro rules, refer to Kakuro I. 입력 The first line of input file has the num

www.algospot.com

  • 게임판의 각 칸은 흰 칸, 검은 칸, 또는 힌트 칸 중 하나이다.
  • 흰 칸에 적절히 숫자를 채워 다음 규칙을 만족시킨다.
    • 모든 흰 칸에는 1~9의 정수가 들어간다.
    • 세로로 연속한 흰 칸의 수를 모두 더하면, 그 칸의 바로 위에 있는 힌트 칸의 왼쪽 아래에 적힌 숫자가 나와야 한다.
    • 가로로 연속한 흰 칸의 수를 모두 더하면, 그 칸의 바로 왼쪽에 있는 힌트 칸의 오른쪽 위에 적힌 숫자가 나와야 한다.
    • 이 때, 한 줄을 이루는 연속된 흰 칸들에는 같은 수를 두 번 넣을 수 없다.
  • 테스트 케이스 C(C <= 50), 게임판의 크기 N(1 <= N <= 20), 검은 칸은 0, 흰 칸은 1, 힌트의 수 Q, 힌트의 정보 y, x(1 <= y, x <= N), direction(0: 가로, 1 : 세로), sum으로 주어진다.
  • 모든 테스트케이스에는 정확히 하나의 정답이 있다.

=> 각 테스트 케이스마다 n줄에 각 n개의 숫자로 모두 채워진 게임판을 출력하기. 검은 칸이나 힌트 칸은 0

 

문제 풀이

이 문제는 각 흰 칸을 방문하면서, 그 칸에 들어갈 수 있는 숫자의 경우의 수를 찾아 배치하는 문제이다.

 

흰 칸 방문 -> 가로 힌트, 세로 힌트 찾기 -> 두 힌트로 넣을 수 있는 공통된 숫자 찾기 -> 배치하기 -> 다음 흰칸 찾기

 

크게 위와 같은 순서로 진행이 되고, 만약 어느 순간 넣을 수 있는 값을 찾지 못하면

이전의 흰 칸으로 돌아가서 숫자를 바꾼 후 다시 진행한다.(재귀)

 

이 과정에서 시간을 줄일 수 있는 곳은 아래 두 가지에서 가능하다.

  1. 가로 힌트, 세로 힌트 찾기
  2. 두 힌트로 넣을 수 있는 공통된 숫자 찾기

 

가로 힌트, 세로 힌트 찾기

한 칸을 방문할 때마다 왼쪽으로, 위쪽으로 탐색을 하면 시간이 많이 소요되기 때문에

leftHintBoard, topHintBoard라는 배열을 만들어 각 위치에 힌트 배열의 index값을 저장하도록 했다.

 

두 힌트로 넣을 수 있는 공통된 숫자 찾기

각 칸에 들어갈 수 있는 수는 다음과 같다.

  1. 1~9인 수
  2. 가로, 세로 힌트의 영향을 받는 칸의 개수로 가로 힌트의 합 값을 만들 수 있는 조합에 해당하는 수
  3. 가로, 세로 힌트에 따른 조합 중 사용하지 않은 수

넣을 수 있는 수는 1~9까지의 수 이므로 비트마스킹을 이용해 충분히 저장 가능하다.

그러니, 2번과 3번에 해당하는 경우의 수를 배열로 미리 만들어 저장해두면

추가적인 탐색 과정 없이 바로 그 칸에 넣을 수 있는 수의 목록을 구할 수 있다.

 

cases[10][46][1024] 배열로

힌트의 영향을 받는 칸의 수, 힌트의 합, 사용된 숫자 목록에 따라 그 칸에 넣을 수 있는 목록을 저장하도록 만들었다.

 

문제 풀이 코드
#include <iostream>
#include <string.h>

using namespace std;

#define MIN(a, b)(a < b ? a : b);
#define MAX(a, b)(a > b ? a : b);

struct hint {
	int sum, usedNum, cnt;	// 합, 사용된 숫자, 힌트를 활용하는 수의 개수
};

bool findAnswer;
int N, Q;
int board[20][20];
int leftHintBoard[20][20];
int topHintBoard[20][20];
int answerBoard[20][20];
hint hints[400];
int cases[10][46][1 << 10];	// 영향을 미치는 칸 수, sum, 사용된 숫자

void solve(int pos) {
	if (findAnswer)
		return;
	int r = pos / N;
	int c = pos % N;
	if (r == N) {	// board 끝
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < N; ++j) {
				cout << answerBoard[i][j] << " ";
			}
			cout << endl;
		}
        findAnswer = true;
		return;
	}
	if (board[r][c]) {	// 흰 칸일 때
		hint& leftHint = hints[leftHintBoard[r][c]];
		hint& topHint = hints[topHintBoard[r][c]];
		int leftUsableNum = cases[leftHint.cnt][leftHint.sum][leftHint.usedNum];
		int topUsableNum = cases[topHint.cnt][topHint.sum][topHint.usedNum];
		int usableNum = leftUsableNum & topUsableNum;
		if (usableNum == 0) return;
		for (int i = 1; i <= 9; ++i) {
			if (usableNum & (1 << i)) {
				leftHint.usedNum |= (1 << i);
				topHint.usedNum |= (1 << i);
				
				answerBoard[r][c] = i;
				solve(pos + 1);

				leftHint.usedNum ^= (1 << i);
				topHint.usedNum ^= (1 << i);
			}
		}
	}
	else solve(pos + 1);
}

void fillHintBoard() {
	for (int i = 1; i < N; ++i) {
		for (int j = 1; j < N; ++j) {
			if (board[i][j]) {	// 흰 칸
				leftHintBoard[i][j] = leftHintBoard[i][j - 1];
				++hints[leftHintBoard[i][j]].cnt;
				topHintBoard[i][j] = topHintBoard[i - 1][j];
				++hints[topHintBoard[i][j]].cnt;
			}
		}
	}
}

int getSize(int set) {	// 1의 개수 세기
	int size = 0;
	for (int i = 1; i <= 9; ++i) {
		if (set & (1 << i))
			++size;
	}
	return size;
}

int getSum(int set) {	// 합 반환
	int sum = 0;
	for (int i = 1; i <= 9; ++i) {
		if (set & (1 << i))
			sum += i;
	}
	return sum;
}

void calHintCases() {
	memset(cases, 0, sizeof(cases));
	for (int set = 0; set < 1024; set += 2) {	// 1 << 0 == 1, 1 << 1 == 2 니까, 2씩 올려야 0을 고려하지 않고 셀 수 있음.
		int l = getSize(set);
		int s = getSum(set);
		int usedNum = set;
		while (true) {
			// l칸의 합이 s이고, 이 칸에 이미 쓰인 숫자의 집합이 usedNum일 때,
			// 나머지 칸에 넣을 수 있는 숫자의 집합을 저장하기
			// 전체 숫자의 집합이 set이 되도록 나머지 숫자 집어넣기
			cases[l][s][usedNum] |= (set & ~usedNum);	// 나머지 칸에 넣을 수 있는 숫자의 집합( (& ~usedNum) : 이미 사용한 숫자 제외)
			if (usedNum == 0) break;	// 이미 쓰인 숫자가 없으면(끝)
			usedNum = (usedNum - 1) & set;	// set의 부분집합 찾기
		}
	}
}

int main() {
	int C;
	cin >> C;
	calHintCases();
	for (int tc = 1; tc <= C; ++tc) {
		findAnswer = false;
		memset(board, 0, sizeof(board));
		memset(leftHintBoard, -1, sizeof(leftHintBoard));
		memset(topHintBoard, -1, sizeof(topHintBoard));
		memset(answerBoard, 0, sizeof(answerBoard));
		cin >> N;
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < N; ++j) {
				cin >> board[i][j];	// 0 : 검은 칸, 힌트 칸 , 1 : 흰 칸
			}
		}
		cin >> Q;
		for (int i = 0; i < Q; ++i) {
			int y, x, dir, sum;
			cin >> y >> x >> dir >> sum;
			if (dir)	// hintBoard에 힌트 index 저장
				topHintBoard[y - 1][x - 1] = i;
			else
				leftHintBoard[y - 1][x - 1] = i;
			hint h = { sum, 0, 0};
			hints[i] = h;
		}
		fillHintBoard();
		solve(0);

	}
	return 0;
}

 

반응형
반응형
문제 설명
 

algospot.com :: ARCTIC

남극 기지 문제 정보 문제 남극에는 N 개의 탐사 기지가 있습니다. 남극의 겨울은 혹독하기 때문에, 남극의 겨울이 찾아오면 탐사 기지들간의 왕래가 중단됩니다. 겨울에도 서로 통신하며 연구

www.algospot.com

  • N개의 탐사 기지가 있다. 그 중 하나는 탐사 본부이다.(N <= 100)
  • 각 기지는 좌표 (x, y)에 위치한다(0 <= x, y <= 1000)
  • 각 기지에는 동일한 출력의 N개의 무전기가 배치된다.
  • 두 탐사 기지 사이의 거리가 D라고 하면 무전기의 출력이 D 이상이어야 통신이 가능하다.
  • 탐사 본부는 다른 기지를 통해 간접적으로 연락할 수 있다.

=> 탐사 본부가 다른 모든 기지에 연락을 할 수 있게 하기 위한 무전기의 최소 출력을 소숫점 밑 셋째 자리에서 반올림해 출력하라.

 

문제 풀이

모든 점을 연결하는 최소 길이 D는 무엇일까? => 길이 D로 모든 점을 연결할 수 있을까?

전자의 경우는 최적화 문제이고, 후자의 경우는 결정 문제이다.

처음에는 최적화 문제로 구하려 했지만 경우의 수가 너무 많아서 시간이 많이 소요되었고,

결정문제로 풀기로 변경했다.

 

답이 될 수 있는 D는 점과 점 사이의 거리 중 하나의 값이 될 것이기 때문에,

모든 거리 값을 구한 후 정렬하여,

가운데 값으로 모든 점이 연결되면 더 작은 값으로, 연결되지 않으면 더 큰 값으로 시도하는 이분법을 활용해 정답을 구했다.

 

문제 풀이 코드
#include <iostream>
#include <cmath>
#include <string.h>

using namespace std;

int N;
double pos[100][2];
double distArray[100][100];
double sortedDistArray[10000];
double temp[10000];

// 거리 계산
int setDistArray() {
	int distCnt = 0;
	for (int i = 0; i < N; ++i) {
		for (int j = i + 1; j < N; ++j) {
			distArray[i][j] = distArray[j][i] = sqrt(pow(pos[i][0] - pos[j][0], 2) + pow(pos[i][1] - pos[j][1], 2));
			sortedDistArray[distCnt++] = distArray[i][j];
		}
	}
	return distCnt;
}

void sort(int start, int end, int mid) {
	int i = start;
	int j = mid + 1;
	int k = start;

	while (i <= mid && j <= end) {
		if (sortedDistArray[i] < sortedDistArray[j])
			temp[k++] = sortedDistArray[i++];
		else 
			temp[k++] = sortedDistArray[j++];
	}

	while (i <= mid) {
		temp[k++] = sortedDistArray[i++];
	}
	while (j <= end) {
		temp[k++] = sortedDistArray[j++];
	}

	for (int l = start; l <= end; ++l) {
		sortedDistArray[l] = temp[l];
	}
}

// 전체 거리값 정렬
void mergeSort(int start, int end) {
	if (start >= end) return;

	int mid = (start + end) / 2;
	mergeSort(start, mid);
	mergeSort(mid + 1, end);
	sort(start, end, mid);
}

bool isVisited[100];
void checkVisit(int index, double dist) {	// 방문 가능 여부 체크
	if (isVisited[index])
		return;
	isVisited[index] = true;
	for (int dest = 1; dest < N; ++dest) {
		if (distArray[index][dest] <= dist)
			checkVisit(dest, dist);
	}
}

double findDist(int start, int end) {	// 이분법으로 최소거리 찾기
	if (start >= end) {
		return sortedDistArray[start];
	}
	int mid = (start + end) / 2;
	memset(isVisited, false, sizeof(isVisited));
	checkVisit(0, sortedDistArray[mid]);
	bool success = true;
	for (int j = 1; j < N; ++j) {
		if (!isVisited[j]) {
			success = false;
			break;
		}
	}
	if (success)	// 거리 중간값으로 모두 통신할 수 있으면
		return findDist(start, mid);
	else
		return findDist(mid + 1, end);
}

int main() {
	int C;
	cin >> C;
	for (int tc = 1; tc <= C; ++tc) {
		cin >> N;
		for (int i = 0; i < N; ++i) {
			cin >> pos[i][0] >> pos[i][1];
		}
		int distCnt = setDistArray();	// 거리값 계산
		mergeSort(1, distCnt - 1);		// 전체 거리값 정렬
		cout << fixed;
		cout.precision(2);
		cout << findDist(0, distCnt - 1) << endl;	// 최소거리 찾기
	}
	return 0;
}

 

반응형