백준 온라인 저지(Baekjoon Online Judge)에서 Bronze I 문제를 이번 포스트까지 포함하여 12문제를 풀어보았으므로, 다음 포스트부터는 Silver 난이도의 문제들을 해결해보도록 하겠습니다.
이번 포스트에서는 1296번 : 데이트, 1308번 : D-Day, 1340번 : 연도 진행바를 풀어보도록 하겠습니다.
1296번 : 데이트
알파벳으로 구성된 문자열을 이용하여 문제에서 제시된 규칙대로 계산하여 얻어진 값을 비교 및 출력하는 문제입니다.
확률 계산 자체는 문제에서 제시된 규칙대로만 계산하면 됩니다.
다만 이 문제에서 특화된 가장 어려운 부분은 확률이 동일한 경우 이름을 알파벳 순으로 나열하여 가장 앞서는 이름을 출력한다는 조건입니다.
string.h에서 이용할 수 있는 함수로는 두 개의 문자열의 순서를 비교할 수 있는 strcmp가 전부인데, 이를 이용하여 가장 앞서는 이름을 찾아야 하기 때문입니다.
#include<stdio.h>
#include<string.h>
int main() {
int n, L1 = 0, O1 = 0, V1 = 0, E1 = 0, L2, O2, V2, E2, max_score = -1, max_count = 0, max_num = 0;
char name1[21], name2[51][21], score[51] = {0, };
scanf("%s\n%d", name1, &n);
for(int i=0; i<strlen(name1); i++) {
if(name1[i] == 'L') L1++;
else if(name1[i] == 'O') O1++;
else if(name1[i] == 'V') V1++;
else if(name1[i] == 'E') E1++;
}
for(int i=0; i<n; i++) {
scanf("%s", name2[i]);
L2 = 0, O2 = 0, V2 = 0, E2 = 0;
for(int j=0; j<strlen(name2[i]); j++) {
if(name2[i][j] == 'L') L2++;
else if(name2[i][j] == 'O') O2++;
else if(name2[i][j] == 'V') V2++;
else if(name2[i][j] == 'E') E2++;
}
score[i] = (L1+L2+O1+O2)*(L1+L2+V1+V2)*(L1+L2+E1+E2)*(O1+O2+V1+V2)*(O1+O2+E1+E2)*(V1+V2+E1+E2)%100;
}
for(int i=0; i<n; i++) if(score[i] > max_score) max_score = score[i];
for(int i=0; i<n; i++) if(score[i] == max_score) max_count++;
if(max_count == 1) {
for(int i=0; i<n; i++) if(score[i] == max_score) printf("%s", name2[i]);
}
else {
for(int i=0; i<n; i++)
if(score[i] == max_score) {
max_num = i;
break;
}
for(int i=max_num+1; i<n; i++) {
if(score[i] == max_score && strcmp(name2[i], name2[max_num]) < 0) max_num = i;
}
printf("%s", name2[max_num]);
}
}
우선 확률을 구하는 과정은 앞서 말한대로 문제에서 제시된 계산식에 기호만 그대로 연산해주면 되기 때문에 크게 어렵지 않습니다.
그리고 문제에서 주어진 OHMINSIK의 이름이 테스트케이스에서 변경될 수도 있으므로 우선 OHMINSIK 또한 별개의 문자열로 받아 L, O, V, E의 수를 count 해주도록 하였습니다.
그 다음 확률이 가장 높은 유일한 이름이 있을 경우 문자열의 순서를 비교하지 않아도 바로 출력해주면 되기 때문에, max_count라는 변수를 이용하여 동률이 존재하는지, 존재한다면 몇 개나 존재하는지를 count 해주었습니다.
만약 max_count가 1개라면 가장 높은 확률을 가진 이름이 한 개라는 뜻이므로 max_score 값에 해당하는 이름을 출력하고 프로그램을 종료해주면 됩니다.
만약 max_count가 2 이상인 경우 가장 큰 문제인 문자열 순서를 구하는 과정은, max_score에 해당하는 이름들을 탐색하다가 찾게 되면 max_score인 이름 두 개의 순서를 비교하고, 그 다음 또 max_score인 이름이 또 있는지 찾고 만약 있으면 현재까지 최고 앞서는 이름과 새 이름을 비교하는 원리로 일일이 비교를 할 수 있도록 구현하였습니다.
1308번 : D-Day
이 문제는 말 그대로 D-Day 기능을 구현하는 문제입니다.
따라서, 윤년에 대한 날짜 계산이 가능하도록 윤년 판별 조건식만 제대로 작성할 수 있으면 어렵지 않게 풀 수 있는 문제입니다.
또한 이 문제의 경우 테스트 케이스를 넣어도 이게 맞는 답인지 알 수가 없는데, 문제 자체가 디데이이므로 디데이 계산기를 찾아 직접 대입해서 비교해보면 됩니다.
https://superkts.com/cal/d_day/
저 같은 경우에도 위의 디데이 계산기 링크를 이용하여 테스트 케이스를 직접 만들어서 코드가 정상적으로 작동하는지 확인해보았습니다.
#include <stdio.h>
int main()
{
int y[3], m[3], d[3], sum[3] = {0, }, day = 0;
for(int i=0; i<2; i++) scanf("%d %d %d", &y[i], &m[i], &d[i]);
if((y[1] - y[0] > 1000) || (y[1] - y[0] == 1000 && m[0] < m[1]) || (y[1] - y[0] == 1000 && m[0] == m[1] && d[0] <= d[1])) {
printf("gg");
return 0;
}
for(int i=0; i<2; i++) {
for(int j=0; j<y[i]; j++) {
sum[i] += 365;
if(j%4 == 0) sum[i]++;
if(j%100 == 0) sum[i]--;
if(j%400 == 0) sum[i]++;
}
if(m[i] > 1) sum[i] += 31;
if(m[i] > 2) sum[i] += 28;
if(m[i] > 2 && y[i]%4 == 0) sum[i]++;
if(m[i] > 2 && y[i]%100 == 0) sum[i]--;
if(m[i] > 2 && y[i]%400 == 0) sum[i]++;
if(m[i] > 3) sum[i] += 31;
if(m[i] > 4) sum[i] += 30;
if(m[i] > 5) sum[i] += 31;
if(m[i] > 6) sum[i] += 30;
if(m[i] > 7) sum[i] += 31;
if(m[i] > 8) sum[i] += 31;
if(m[i] > 9) sum[i] += 30;
if(m[i] > 10) sum[i] += 31;
if(m[i] > 11) sum[i] += 30;
sum[i] += d[i];
}
printf("D-%d", sum[1]-sum[0]);
}
이 문제를 해결하는데 제가 사용한 핵심 아이디어는 연/월/일의 날짜를 1년 1월 1일부터 몇 일이 지났는지를 시작일, 종료일 모두 count하여 빼준 것입니다.
만약 시작일로부터 날짜를 0에서부터 더해가며 날짜수를 계산할 경우 고려해야할 변수가 너무 많아지기 때문에 문제를 쉽게 해결하기 어렵습니다.
따라서 이전에 풀었었던 시계 뺄셈 문제와 같은 원리로 1년 1월 1일부터 누적 날짜가 얼마나 되었는지를 계산하는 방식으로 푸는 것이 편리합니다.
우선 연 단위부터 하여 윤년이 아닌 해는 365일을, 윤년인 해는 366일을 더해주었으며, 연까지 계산이 끝나면 이제 월 단위로 날짜 계산을 해야합니다.
월 단위의 경우 월마다 규칙성을 일반화하기 힘들게 날짜가 배치되어있기 때문에 그냥 if문 여러개를 만들어 예를 들어 8월이면 7월까지의 날짜가 모두 더해지도록 구현하였습니다.
이 때 2월의 경우 해당 해에 28일까지 있을수도 있고 29일까지 있을수도 있기 때문에 여기서도 윤년 고려를 한 번 더해주어야 합니다.
마지막에는 sum[1]에서 sum[0]을 빼서 종료일과 시작일의 날짜 차이를 계산하여 출력해주면 됩니다.
이 문제에서는 주의해야할 점이 문제에서 제시해준대로 윤년 조건식을 if, else if 문으로 작성하면 틀린다는 것입니다.
반드시 표를 그려보거나 모든 경우의 수를 확인하여 조건식이 제대로 판별이 되도록 작성되었는지 확인해보아야 합니다.
1340번 : 연도 진행바
이 문제도 위의 D-day 문제와 비슷하게 윤년을 고려하여 날짜 수를 세주어야 하는 문제입니다.
월과 일, 해당년도와 시, 분을 입력했을 때 해당 년이 윤년인지까지 판단하여 전체 년 중에서 해당 시각에서 해당 해가 몇 퍼센트나 지났는지 아주 작은 오차까지 고려하여 출력하는 문제입니다.
이 문제는 연을 누적하여 계산할 필요는 없으나, 대신 시와 분까지 추가되어 계산이 조금 더 복잡해질 수는 있습니다.
#include<stdio.h>
#include<string.h>
int main()
{
int year, month, day, hour, min;
double sum = 0, total;
char word[20];
scanf("%s %d, %d %02d:%02d", word, &day, &year, &hour, &min);
if(!(year%400) || (!(year%4) && (year%100))) total = 366;
else total = 365;
if(!strcmp(word, "January")) month = 1;
else if(!strcmp(word, "February")) month = 2;
else if(!strcmp(word, "March")) month = 3;
else if(!strcmp(word, "April")) month = 4;
else if(!strcmp(word, "May")) month = 5;
else if(!strcmp(word, "June")) month = 6;
else if(!strcmp(word, "July")) month = 7;
else if(!strcmp(word, "August")) month = 8;
else if(!strcmp(word, "September")) month = 9;
else if(!strcmp(word, "October")) month = 10;
else if(!strcmp(word, "November")) month = 11;
else if(!strcmp(word, "December")) month = 12;
if(month > 1) sum += 31;
if(month > 2 && (!(year%400) || (!(year%4) && (year%100)))) sum += 29;
else if(month > 2) sum += 28;
if(month > 3) sum += 31;
if(month > 4) sum += 30;
if(month > 5) sum += 31;
if(month > 6) sum += 30;
if(month > 7) sum += 31;
if(month > 8) sum += 31;
if(month > 9) sum += 30;
if(month > 10) sum += 31;
if(month > 11) sum += 30;
sum = (sum - 1 + day)*24*60 + hour*60 + min;
total = total*24*60;
printf("%.10f", 100*sum/total);
}
월에 대한 입력이 문자열로 주어지므로, 어쩔 수 없이 string.h의 strcmp 함수를 이용하여 12개의 월을 수치로 변환시켜주는 부분이 길어질 수 밖에 없습니다.
어쨌든 문자열로 입력된 달의 이름을 숫자로 바꾸었으면, 위의 D-day 문제와 같이 해당 년 중에서 몇 일이 지난 것인지 일 단위로 변환시킵니다.
그 다음 시와 분을 어차피 같이 계산해야하기 때문에 해당년도의 총 분 수를 구하고, 지금까지 지난 총 분 수를 구하여 퍼센트를 계산해주면 됩니다.
이 때 float 자료형을 사용할 경우 소숫점 9번째 자리까지 값이 정확하게 나타나지 않기 때문에, 반드시 double형의 자료형을 사용하고, 출력할 때도 %.10f 등으로 자릿수를 지목하여 출력하는 것이 좋습니다.