민팽로그

[백준 / BOJ] 1654: 랜선 자르기 본문

PS/백준📖

[백준 / BOJ] 1654: 랜선 자르기

민팽 2022. 3. 31. 00:33

https://www.acmicpc.net/problem/1654

 

1654번: 랜선 자르기

첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그

www.acmicpc.net

 

 


 

 

문제


집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.

이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm는 버려야 한다. (이미 자른 랜선은 붙일 수 없다.)

편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. N개보다 많이 만드는 것도 N개를 만드는 것에 포함된다. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오.

 

입력


첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 231-1보다 작거나 같은 자연수이다.

 

출력


첫째 줄에 N개를 만들 수 있는 랜선의 최대 길이를 센티미터 단위의 정수로 출력한다.

 

예제 입력


4 11
802
743
457
539

 

예제 출력


200

 

코드 


#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

vector<int> v;

long long find_max_length(int n, int k, int max_value) {
	long long start = 1, end = max_value, mid, num, res = 0;

	while (start <= end) {
		mid = (start + end) / 2;

		num = 0;
		for (int i = 0; i < k; i++) num += (v[i] / mid);

		if (num >= n) {
			start = mid + 1;
			res = max(res, mid);
		}
		else end = mid - 1;
	}
	return res;
}

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

	int k, n;

	cin >> k >> n;
	v.assign(k, 0);

	int max_value = 0;
	for (int i = 0; i < k; i++) {
		cin >> v[i];
		max_value = max(max_value, v[i]);
	}

	cout << find_max_length(n, k, max_value) << '\n';

	return 0;
}

 

좀 더 복잡한 코드..

더보기

1) 반복문 탈출이 깔끔하지 않은 코드

#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

vector<int> v;

long long find_max_length(int n, int k, int max_value){
	long long start = 0, end = max_value, mid, num;

	while (start < end) {
		mid = (start + end) / 2;

		if (mid == start) {
			long long num2 = 0;
			for (int i = 0; i < k; i++) num2 += (v[i] / end);

			if (num2 == n) return end;
			else return start;
		}

		num = 0;
		for (int i = 0; i < k; i++) num += (v[i] / mid);

		if (num >= n) start = mid;
		
		else end = mid;
	}
	return end;
}

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

	int k, n;

	cin >> k >> n;
	v.assign(k, 0);

	int max_value = 0;
	for (int i = 0; i < k; i++) {
		cin >> v[i];
		max_value = max(max_value, v[i]);
	}

	cout << find_max_length(n, k, max_value) << '\n';

	return 0;
}

 

2) start의 최댓값을 찾아 범위를 지정 -> 더 오래걸리는 코드

#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;

vector<int> v;

long long find_max_length(int n, int k) {
	long long start = 0, end = v[k - 1], mid, num;
	for (int i = 0; i < k; i++) {
		num = 0;
		for (int j = i; j < k; j++) {
			num += (v[j] / v[i]);
		}

		if (num >= n) start = v[i];
	}

	while (start < end) {
		mid = (start + end) / 2;
		if (mid == start) {
			long long num2 = 0;
			for (int i = 0; i < k; i++) num2 += (v[i] / end);
			if (num2 == n) return end;
			else return start;
		}

		num = 0;
		for (int i = 0; i < k; i++) num += (v[i] / mid);


		if (num >= n) {
			start = mid;
		}
		else end = mid;
	}
	return end;
}

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

	int k, n;

	cin >> k >> n;
	v.assign(k, 0);

	for (int i = 0; i < k; i++) {
		cin >> v[i];
	}

	sort(v.begin(), v.end());
	cout << find_max_length(n, k) << '\n';

	return 0;
}

 

 


 

 

이분탐색 연습이 더 필요한 것 같다(반복문 탈출과 리턴값을 오래 고민하지 않도록 연습). 복잡하게 짰던 코드들은 내가 왜 이런 생각을 했는지 나중에 다시 보기 위해 남겨두었다.

 

일단 나는 이분탐색 문제라는 것을 알고 시작했기 때문에 문제를 보자마자 풀이방법은 금방 떠올랐다.

일단 이분탐색을 사용하지 않는다면 가장 긴 길이부터 1씩 줄여가며 순차적으로 탐색해야 한다. 이렇게 풀게 된다면 시간 복잡도가 O(N * K) 쯤 나올 것이다. N과 K의 값이 크다면 제한 시간 안에 풀기 힘든 시간 복잡도이다. 이분탐색은 시간 복잡도가 O(logN)이므로 주어진 문제를 O(k * logN)의 시간 복잡도로 풀 수 있다.

그리고 길이가 a인 랜선을 만든다고 할 때 a가 가질 수 있든 최대값을 구하기 위해서 다음 조건을 만족해야 한다.

k(1) / a + k(2) / a + ... + k(K) / a >= n 를 만족하는 가장 큰 a값
(k(i): i번째 입력으로 주어진 랜선의 길이, i = 1, 2, ..., K)

 

 

접근까지는 쉬웠으나 문제에 대한 이해가 부족해서 처음에 많이 헤맸다. 일단 내가 오랫동안 잘못 생각했던 내용은 다음 2가지 이다.

 

1. 이분 탐색의 end 초기값이 가지고 있는 랜선들 중 가장 짧은 길이의 값이어야 한다고 생각했다.

- 문제에서 모든 랜선을 사용해야 한다고 하지 않았다. 가지고 있는 랜선 중 가장 짧은 길이의 랜선에 맞추어 랜선들을 잘랐을 때, N보다 훨씬 많은 갯수가 나올 수도 있다. 이럴땐 더 긴 길이로 자르고 해당 랜선은 사용하지 않으면 된다. 즉 버릴 수 있다는것!

 

2. 주어지는 입력이 모두 int 범위이므로 문제를 풀기 위해 사용되는 모든 변수들의 자료형을 int로 선언해도 문제 없다고 생각했다.

- 정말 기본적인 내용을 틀렸다. mid를 구하는 과정 즉 (start + end)의 과정에서 오버플로우가 발생할 수 있다. 이런 기본적인 개념은 틀리지 않도록 항상 주의하자ㅠㅠ!!!

 

 

'PS > 백준📖' 카테고리의 다른 글

[백준/BOJ] 9663번: N-Queen  (0) 2022.05.06
[백준 / BOJ] 2805: 나무 자르기  (0) 2022.04.04
[백준/BOJ] 1920: 수 찾기  (0) 2022.03.14
[백준/BOJ] 7579: 앱  (0) 2022.03.13
[백준 / BOJ] 2293: 동전 1  (0) 2022.03.03
Comments