0. 문제

문제 링크

1. 풀이

학생들이 점심을 먹기 위해 일렬로 서 있을 때 줄 안의 특정 지점에서 콘서트를 열면 모든 학생들이 콘서트를 구경하러 오게 되는데, 이 때 모든 학생들이 콘서트에 참가하기 위해 최소한으로 움직일 수 있는 콘서트 지점을 구하는 문제이다.

따라서, 만약 줄 안에 학생이 단 한 명만 있다면 그 학생이 콘서트를 들을 수 있는 범위 안에서 콘서트를 열면 학생이 전혀 움직일 필요가 없으므로 이동거리가 최소(0)가 되어 정답이 되는 것이다.

콘서트를 개최하는 위치 PP 에서 콘서트를 열었을 때 학생들이 이동해야 하는 거리 총합을 함수 f(P) 로 표현할 수 있는데, 이렇게 표현하게 되면 문제의 조건을 함수 그래프로 그려낼 수 있다.

예를 들어, 문제의 첫 번째 예시는 다음처럼 나타낼 수 있다.

1
0 1000 0

학생이 단 한 명이고, 해당 학생이 콘서트를 들을 수 있는 범위가 0이기 때문에 학생이 위치한 바로 그 지점에서 콘서트를 열면 이동 거리 합이 0이 되므로 정답이 된다.


동일한 방식으로 두 번째 예시는 다음처럼 나타낼 수 있다.

2
10 4 3
20 4 2

두 번째 예시에선 학생이 두 명이고, 각각 1020 에 위치하는데, 공교롭게도 f(13)f(16)이 같이 최솟값을 가진다.

따라서 이 문제는 함수 f(P)를 구해 해당 함수의 최솟값을 출력하면 되는 문제이다.

이렇게 최솟값이 존재하는 함수(대표적으로 이차 함수)의 최솟값을 구해내고자 하는 경우 삼분 탐색(Ternary Search) 가 유용하게 이용된다.

이분 탐색범위를 절반씩 줄여 나가면서 원하는 지점을 탐색하는 알고리즘이라면, 삼분 탐색범위를 13씩 줄여 나가면서 원하는 지점을 탐색한다.


간단히 예를 들자면

임의의 최솟값이 존재하는 함수 g(x)가 다음과 같을 때, 범위 x13씩 나누어 준다.

이후 g(leftmid)g(rightmid)를 비교해 g(leftmid)가 더 크다면 endright-mid로, 그렇지 않다면 startleft-mid로 옮긴다.

이후, 위 알고리즘을 반복한다.

...

삼분 탐색을 알고 있던 사람이라면 위 설명을 듣고 의아할 수 있는데, 일반적인 삼분 탐색(함숫값을 찾는 것이 아닌 정렬된 데이터에서 값을 찾는 용도)의 경우 left-midright-mid 가 같은 값을 가질 경우 start, end 모두를 수정하지만, 이 문제는 g(x)가 정렬되어 있지 않기 때문에 다음과 같은 반례가 있을 수 있다.

마찬가지로, 최솟값이 유일하지 않을 수 있다는 반례 때문에 일반적인 삼분 탐색처럼 startend보다 작거나 같은 동안 탐색을 계속 반복하는 방식은 무한 루프를 유발할 수 있다(두 학생이 3031에 서 있다고 상상해 보자). 그러므로 최솟값을 찾을 수 있을 정도의 충분한 횟수만큼만 삼분 탐색을 반복하도록 루프를 설정해야 한다.

2. 코드

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cmath>

using namespace std;

constexpr int MAXN = 200'001;
constexpr int MAXD = 1e9;

int n;
int p[MAXN];
int w[MAXN];
int d[MAXN];

long long calc(int x)
{
	long long sum = 0;
	for (int i = 0; i < n; i++)
	{
		if (abs(x - p[i]) <= d[i])
		{
			continue;
		}
		long long hearingDist = min(abs((x - d[i]) - p[i]), abs((x + d[i]) - p[i]));
		sum += hearingDist * (long long)w[i];
	}
	return sum;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	//freopen("input.txt", "r", stdin);

	cin >> n;
	int s = MAXD;
	int e = 0;
	for (int i = 0; i < n; i++)
	{
		cin >> p[i] >> w[i] >> d[i];
		s = min(s, p[i]);
		e = max(e, p[i]);
	}
	if (n == 1)
	{
		cout << 0;
		return 0;
	}
	
	long long sum = 1e18;
	for(int i=0;i<60;i++)
	{
		int delta = (e - s) / 3;
		int l = s + delta;
		int r = e - delta;
	
		long long lr = calc(l);
		long long rr = calc(r);
		sum = min(lr, sum);
		sum = min(rr, sum);
		if (lr > rr)
		{
			s = l;
		}
		else
		{
			e = r;
		}
	}
	cout << sum;
	
	return 0;
}

3. 결과