조합론과 분할 정복 기반 거듭제곱을 응용한 문제다. Large 케이스에서 N이 최대 300,000까지 들어올 수 있고, 모든 조합을 하나하나 고려한다면 지수시간에 해당하는 시간 복잡도를 가지기 때문에 브루트 포스는 사용할 수 없다.
하지만, 문제를 잘 생각해 보면 알 수 있듯이 주헌고통지수라는 것은 특정 조합에서의 최댓값과 최솟값만 있어도 구해낼 수 있다. 그리고 모든 메뉴를 스코빌 지수 기준으로 정렬해 둔다면, 어떤 메뉴가 최댓값이 되는 경우와 최솟값이 되는 경우를 바로 구해낼 수 있다.
만약 예제 2번의 경우에서, 6이 최댓값이 되는 경우를 생각하면 위처럼 생각할 수 있다. 6은 조합에 포함되어야 하고, 10은 포함되어서는 안 된다. 그리고 나머지 더 작은 수들에 대해서는 조합 내 포함여부가 6이 최댓값이 되는 데에 영향을 주지 않는다. 하지만 저렇게 구해내게 되면 6만 원소로 포함된 조합의 경우도 포함된다. 하지만 문제 내용에 의해서 다른 음식이 존재해야만 주헌고통지수를 구해낼 수 있으므로, -1을 해주어 그 예외 경우를 빼준다.
최솟값은 당연히 이를 반대로 생각해서 구하면 된다. 결국 필요한 것은 메뉴 리스트의 정렬 이후, 각 인덱스별 값이 최댓값과 최솟값이 되는 경우를 2의 거듭제곱을 통해 구하고 그 둘의 차이만큼 값에 곱해 결과에 더해주면 된다(최댓값이 되는 경우에는 주헌고통지수의 합에 더해졌을 것이고, 최솟값이 되는 경우에는 주헌고통지수의 합에서 빼졌을 것이다).
단, 여기서 거듭제곱 구하는 과정을 브루트 포스로 하게 되면, N 값이 커졌을 때 시간 초과가 날 것이다. 따라서 분할 정복을 통해 거듭제곱을 구하는 테크닉을 사용해 시간 복잡도를 줄여 문제를 해결할 수 있다.
#include <iostream>
#include <algorithm>
#define MOD 1000000007
using namespace std;
long long menuList[300000];
long long bigPow(long long base, long long exp) {
if(exp==0) return 1;
if(exp==1) return base;
long long half = bigPow(base, exp/2);
if(exp%2==0) {
return half*half%MOD;
} else {
return half*half%MOD*base%MOD;
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
for(int i=0; i<n; i++) {
cin >> menuList[i];
}
sort(menuList, menuList+n);
long long result = 0;
for(int i=0; i<n; i++) {
long long maxCnt = bigPow(2, i)-1;
long long minCnt = bigPow(2, n-i-1)-1;
result += menuList[i]*(maxCnt-minCnt);
result %= MOD;
}
cout << result;
}
'Dev > BOJ' 카테고리의 다른 글
[백준 14428] 수열과 쿼리 16 (1) | 2024.12.06 |
---|---|
[백준 2243] 사탕상자 (0) | 2024.11.27 |
[백준 16565] N포커 (0) | 2024.11.12 |
[백준 17401] 일하는 세포 (1) | 2024.11.05 |
[백준 30805] 사전 순 최대 공통 부분 수열 (0) | 2024.11.04 |