BOJ 풀이

[BOJ/백준 13904/C++] 과제

Vfly 2022. 12. 20. 22:59

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

 

13904번: 과제

예제에서 다섯 번째, 네 번째, 두 번째, 첫 번째, 일곱 번째 과제 순으로 수행하고, 세 번째, 여섯 번째 과제를 포기하면 185점을 얻을 수 있다.

www.acmicpc.net

 

[문제 요약]

- 하루에 한 과제를 끝낼 수 있는데, 과제마다 마감일이 있으므로 모든 과제를 끝내지 못할 수도 있다. 

- 과제마다 끝냈을 때 얻을 수 있는 점수가 있는데, 마감일이 지난 과제는 점수를 받을 수 없다.

- 가장 점수를 많이 받을 수 있도록 과제를 수행하고 싶다.

- 얻을 수 있는 점수의 최댓값을 구하시오.

 

[문제 조건]

- 첫 줄에 정수 N (1 ≤ N ≤ 1,000)이 주어진다.

- 다음 줄부터 N개의 줄에는 각각 두 정수 d (1 ≤ d ≤ 1,000)와 w (1 ≤ w ≤ 100)가 주어진다. 

  • d는 과제 마감일까지 남은 일수를 의미하며, w는 과제의 점수를 의미한다.

- 시간 제한 : 1초

- 메모리 제한 : 256MB

 

[문제 풀이]

이런 문제처럼 작업을 스케쥴링 하는 문제들은 여러가지로 풀 수 있지만 필자는 처음에 두가지 방법으로 접근을 시도한다.

 

  • 작업들을 끝냈을때 얻을 수 있는 코스트들이 높은 순서대로 정렬하고 시작.
  • 가장 먼저 끝나는 기준으로 정렬 후 시작.

위 두 가지 방법으로 접근을 한다.

 

근데 여기서 2번째 방식으로 접근을 시도하면 문제가 풀리지 않는다.

 

당장 문제에서 주어진 예제를 살펴보자.

7
4 60
4 40
1 20
2 50
3 30
4 10
6 5

위 예제에서 먼저 끝나는 순서대로 선택하면(같은 경우 점수가 높은거) (1,20) → (2,50) → (3,30) → (4,60) → (6,5)를 선택하게 될텐데 이때 얻을 수 있는 점수는 165점이다. 근데 정답은 185점이니 사실상 이 방법으로는 못 푼다는 얘기다.

 

따라서 작업들을 점수가 높은 순서대로 정렬하고, 그 작업을 추가했을때 작업들을 올바르게 끝낼 수 있는지 확인하며 추가해준뒤 점수를 다 더해보았다.

 

bool cmp(pii& a, pii& b)
{
	return a.second > b.second;
}

///////////////////////////////////
......
///////////////////////////////////

for (int i = 0; i < N; i++)
{
    cin >> dt[i].first >> dt[i].second;
}

sort(dt.begin(), dt.end(), cmp);

pii는 pair<int,int>다.

 

점수들이 높은 순서대로 정렬을 먼저 한 뒤.

 

int ans = dt[0].second;
vector<int> ans_d;
ans_d.push_back(dt[0].first);
for (int i = 1; i < N; i++)
{
    vector<int> dummy = ans_d;

    dummy.push_back(dt[i].first);
    sort(dummy.begin(), dummy.end());

    bool flag = true;
    for (int j = 0; j < dummy.size(); j++)
    {
        if (dummy[j] >= j + 1) continue;
        else { flag = false; break; }
    }
    if (!flag) continue;
    else
    {
        ans_d.push_back(dt[i].first);
        ans += dt[i].second;
    }
}
cout << ans;

작업들을 하나씩 추가하고, 마감일만을 따로 배열에 저장 후 오름차순으로 정렬 한 뒤 인덱스의 순서와 작업의 마감기간중 인덱스 값이 더 큰 경우가 존재하면 그 작업조합은 수행할 수 없다는 것을 의미함으로 추가하지 않았다.

 

문제에서 주어진 예제를 통해 설명하면

4 60
2 50
4 40
3 30
1 20
4 10
6 5

정렬을 하면 위와 같이 되어있을 것이고, 순서대로 집어넣는다면 중간에 4,2,4,3까지는 선택되었을 것이다. 여기서 1을 추가하고 정렬을 하면 1,2,3,4,4가 되는데 여기서 마지막 4의 작업이 5일차때 수행할 수 있음으로 이 조합은 수행할 수 없는 조합이다. 그래서 최종적으로는 4,2,4,3,6 작업(정렬 전 : 다섯 번째, 네 번째, 두 번째, 첫 번째, 일곱 번째 작업)이 선택될 것이다.

 

[소스 코드1]

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <cmath>
#include <deque>
#include <list>
#include <math.h>
#include <map>
#include <queue>
#include <stack>

#define pii pair<int,int>

using namespace std;

int N;
vector<pii> dt;

bool cmp(pii& a, pii& b)
{
	return a.second > b.second;
}

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

	cin >> N;
	dt.resize(N);
	for (int i = 0; i < N; i++)
	{
		cin >> dt[i].first >> dt[i].second;
	}

	sort(dt.begin(), dt.end(), cmp);

	int ans = dt[0].second;
	int day = 0;
	vector<int> ans_d;
	ans_d.push_back(dt[0].first);
	for (int i = 1; i < N; i++)
	{
		vector<int> dummy = ans_d;

		dummy.push_back(dt[i].first);
		sort(dummy.begin(), dummy.end());

		bool flag = true;
		for (int j = 0; j < dummy.size(); j++)
		{
			if (dummy[j] >= j + 1) continue;
			else { flag = false; break; }
		}
		if (!flag) continue;
		else
		{
			ans_d.push_back(dt[i].first);
			ans += dt[i].second;
		}
	}
	cout << ans;
}

 

음...

 

풀긴했는데 시간이 맘에 안든다.

 

추가적으로 한번 시간을 줄여보도록하자.

 

당연히 시간이 오래 걸리는 곳이 작업을 선택하는 부분일 것이다.

 

선택하는 방법을 아래와 같이 한번 바꿔보았다.

vector<bool> ans_d;
	int ans = 0;
	ans_d.resize(max_day+1);
	for (int i = 0; i < N; i++)
	{
		for (int j = dt[i].first; j >= 1; j--)
		{
			if (ans_d[j]) continue;
			else
			{
				ans += dt[i].second;
				ans_d[j] = true;
				break;
			}
		}
	}

1부터 가장 긴 마감일까지 배열을 만들고, 작업을 추가할건데 작업이 선택되면 마감일부터 1씩 줄여가면서 배열에 저장해준다.

 

간단하게 설명하자면, 문제에서 주어진 예제가 있고 정렬이 진행된 후에 (4,60)짜리 작업을 선택하고 4일차에 배치했다는 의미로 ans_d[4]를 true로 만들어준다.

 

 

다음 (2,50)짜리 작업을 선택하고 2일차에 배치했다는 의미로 ans_d[2]를 true로 만들어준다.

 

다음 (4,40)짜리 작업을 선택하고 4일차에 배치하려고 했는데 이미 앞에서 4일차에는 작업이 배치되어 있다. 여기서 하루씩 줄여나가면서 빈 날에 이 작업을 추가하면 3일차에 작업을 배치해준다. ans_d[3]를 true로 만들어준다.

 

다음 (3,30)짜리 작업을 선택하고 3일차에 배치하려고 했는데 이미 2일차,3일차에 작업이 있기 때문에 1일차에 작업을 배치한다. ans_d[1]를 true로 만들어준다.

 

다음(1,20)짜리 작업을 1일차에 배치하려고 했는데 1일차에는 이미 작업이 있고 가장 빠른날이기 때문에 이 작업은 배치가 불가능하기 때문에 넘어간다.

 

다음(6,5)짜리 작업은 6일차에 배치해준다. ans_d[6]를 true로 바꿔준다.

 

 

얻을 수 있는 점수들은 작업이 배치될때 , true로 바꿔주는 연산이 일어날때 ans변수에다가 계속 더해준 뒤 마지막에 출력하면 정답이 된다.

 

[소스 코드2]

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <cmath>
#include <deque>
#include <list>
#include <math.h>
#include <map>
#include <queue>
#include <stack>

#define pii pair<int,int>

using namespace std;

int N, max_day = 0;
vector<pii> dt;

bool cmp(pii& a, pii& b)
{
	return a.second > b.second;
}

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

	cin >> N;
	dt.resize(N);
	for (int i = 0; i < N; i++)
	{
		cin >> dt[i].first >> dt[i].second;
		max_day = max(max_day, dt[i].first);
	}

	sort(dt.begin(), dt.end(), cmp);
	
	vector<bool> ans_d;
	int ans = 0;
	ans_d.resize(max_day+1);
	for (int i = 0; i < N; i++)
	{
		for (int j = dt[i].first; j >= 1; j--)
		{
			if (ans_d[j]) continue;
			else
			{
				ans += dt[i].second;
				ans_d[j] = true;
				break;
			}
		}
	}
	cout << ans;
}