티스토리 뷰
2012 google codejam round 1A : http://code.google.com/codejam/contest/1645485/dashboard
대회 후기
오전 8시 30분에 시작인 줄 알고 8시에 겨우 일어났는데 10시부터 시작이었다 -_-;;; 뭐 여튼 잠도 좀 깨고, 새로나온 구글 드라이브 구경 좀 하다가(WeVideo 라는 app 이 참 좋더라 -0-) 10시부터 시작했다.
역시나 문제의 정확한 해석이 힘들었다. 여태까지 영어를 대략적인 의미로만 파악하다가 조동사 하나 하나가 중요한 '문제'를 풀려니 ㅠㅜ 1번 문제의 의미를 파악하고 나니 30분이 지났다. 나중에 확인해보니 round 1A 1등한 사람은 2번 large 문제까지 다 푼 시간... 난 무엇을 하고 있는것인가 ㅠㅜ
round 1 은 A,B,C 3개 각 round 의 1000등까지를 round 2 로 보내준다. 난 1번 small 문제 겨우 풀고(문제 해석에 30분, 문제 풀이에 1시간 30분이 걸림 ㅠㅜ)
20점(1번 small, large 둘다 풀면)이면 3000등 안에 드는데, 난 10점. 뒤에서 100등 ㅠㅜ. large 문제는 맞췄다고 생각했는데 2가지의 실수를 했다. 소수점 계산을 하는데 float 을 쓴 거랑 재귀함수(recursive function)로 문제를 풀었다는 것. 소수점 나오면 무조건 double 을 써야하고, 재귀함수는 for loop 과 배열로 계산했었어야 했다. 다음번에는 실수하지 말자 ㅠㅜ
일단 추후의 대회나, 처음 (취미로) 코드잼을 시작하는 사람을 위해 구글 문서(https://docs.google.com/document/d/1gfJrZdn_dOxwL3cyfYw7iqezsuyI89SnB0yY-pxNoZU/edit) 하나 만들어 둔다. 계속 정리하고, 보완할 예정~
문제 내용
2012년 Google Codejam round 1 A - Password problem
좀 더 풀어보면, 현재까지 입력한 암호의 자리수와 전체 암호의 자리수, 그리고 각 자리의 키를 잘 입력할 확률이 주어졌을 때, 가장 작은 expected 값을 구하는 것이다. expected 값은 특정 상황에서 몇번 더 키를 입력해야하는지에 대한 값이며, 다음과 같이 구할 수 있다. 단, 현재 상황에서 '백스페이스키로 한자리를 지우거나', '엔터키로 현재 입력을 그냥 넣어서 틀리고 다시 더 입력할 수 있다', 그리고 '현재 상황 이후의 모든 입력은 100% 제대로 입력한다', '백스페이스, 엔터키는 키를 한 번 더 입력한 것으로 취급한다' '특히 암호를 다 입력한 마지막은 항상 엔터키를 입력한다'
만약 암호가 "guest" 이고 현재 2자리의 암호를 입력했고, 첫번째 자리를 제대로 입력할 확률이 60%, 두번째 자리도 60% 라면, 위와 같이 expected 값을 구할 수 있다. "gu" 는 제대로 "gu" 를 입력했다는 뜻이고, "gX" 는 "g" 는 제대로 입력했고, "X" 는 "u" 를 입력해야하는 자리에 다른 글자를 입력했다는 뜻이다.
위의 그림은 다음과 같은 입력값과 출력값을 가지게 된다.
입력:
2 5
0.6 0.6
출력:
7.0
"guest" 는 5개의 글자이고, 현재까지 입력된 문자는 2개 이므로, "2 5" 로 한 줄 입력을 받게 되고, 첫째 자리를 제대로 입력할 확률이 60% -> 0.6, 두번째 자리도 60% 이므로 0.6이 된다.
그리고 위의 그림에서 Expected 값이 가장 작은 값이 7 이므로, 7.0을 출력하게 된다.
위의 그림을 좀 더 분석해보면, Probability "gu" 는 첫째자리도 맞고, 둘때자리도 맞으므로 0.6 * 0.6 = 0.36 의 확률이다. "gX", "Xu", "XX" 는 마찬가지.
Keystrokes if I keep typing 은 현재 "gu", "gX" 등을 입력했는 상황에서 각각 몇번 더 키를 입력해야 암호를 맞출 수 있다는 것이다. "gu" 가 4 인 이유는 e,s,t,엔터 를 더 입력하면 되므로 4번. "gX" 는 e,s,t,엔터 입력하면 암호가 틀리게 되므로 여기서 다시 g,u,e,s,t,엔터 를 입력해서 4번+6번해서 10번이다.
Keystrokes if I press backspace once 중 "gu" 는 백스페이스,u,e,s,t,엔터 이므로 6번. "Xu" 는 백스페이스,u,e,s,t,엔터 하면 암호가 틀리므로 다시 g,u,e,s,t,엔터. 그래서 6번+6번해서 12번 이런식이다.
Keystrokes if I press enter right away 는 모두 7 인 이유가 엔터를 바로 넣어서 현재 입력중인 암호는 틀려버리고 다시 g,u,e,s,t,엔터 해서 1번+6번 = 7번이다.
아... 다시 적어보니 이것도 쉬운 로직은 아니구나 ㅋ
문제해결과정
이 문제를 저렇게 진짜 테이블을 다 그리려고 하니 머리가 아프기도 하고, 저 테이블을 진짜로 그리려면, large 문제를 도저히 해결할 수 없겠다는 생각이 들었다(large 문제는 암호의 길이가 10만이므로 이것을 제곱한 만큼 하려면 도저히 풀 수가 없다..... 사실 10만 제곱이 얼만큼 걸리는가해서 그냥 이중for 를 돌려봤는데 꽤 오래걸림;; 그리고 여기서 다시 10만 번을 돌아야하므로 일단 저 테이블을 그리는 건 포기.
그래서 다시 손으로 문제를 계속 풀어보기 시작함. 분명히 무슨 규칙이 있을거라는 생각에... 일단은 '가지치기' 가 생각나서, 특정 최소 값을 구하고 나머지는 표를 안 그래도 될 거 같다는 생각을 해봤는데, 결과값이 '계산된 값' 이라서 '어? 그럼 그려야하나? -_-' 싶었는데 이것도 아니었음.
다시 이런저런 생각을 해보다가 문득 떠오른 것이 '백스페이스를 한번 하면 이전 상태가 된다' 였음. 즉, 현재 입력된 암호가 5개이고, 백스페이스를 한번 입력하면, '입력된 암호가 4개인 상태' 가 된다는 것. 결과적으로는 고등학교 때 배운 점화식 문제;; An = An-1 + 9 같은 ㅋ
결론적으로는 테이블을 3부분으로 나누어보았다.
- Keep : Keystrokes if I keep typing
- Backspace : Keystrokes if I press backspace once twice.......
- EnterNow : Keystrokes if I press enter right away
#include <algorithm>#include <vector>#include <iostream>#include <sstream>using namespace std;stringstream emptyOut;#define DP_ON 0#if DP_ON#define DP std::cout << "[DebugPrint]:"#else#define DP emptyOut#endifint getintline(){string data;getline(cin, data);stringstream s;s << data;int ret;s >> ret;return ret;}vector < int > getintvectorline(){vector < int > ret;string data;getline(cin, data);stringstream s, debugstream;s << data;debugstream << "getintvector : ";while ( !s.eof() ){int v;s >> v;debugstream << " " << v;ret.push_back(v);}DP << debugstream.str().c_str() << endl;return ret;}vector < double > getdoublevectorline(){vector < double > ret;string data;getline(cin, data);stringstream s, debugstream;s << data;debugstream << "getintvector : ";while ( !s.eof() ){double v;s >> v;debugstream << " " << v;ret.push_back(v);}DP << debugstream.str().c_str() << endl;return ret;}vector < double > probabilities;double expected(double currentTyped, double totalCharacters){if ( currentTyped == 0 ){return totalCharacters + 1.0;}double expected_previous = expected(currentTyped-1, totalCharacters) + 1;double keepTyping = 0.0f;{// calc keepdouble all_ok_probability = 1.0;for ( int i=0; i<currentTyped; ++i ){all_ok_probability *= probabilities[i];}keepTyping = all_ok_probability * (totalCharacters-currentTyped+1.0);keepTyping += (1.0-all_ok_probability) * (totalCharacters-currentTyped+1.0+totalCharacters+1.0);}double enterNow = 1.0 + totalCharacters + 1.0;double min = expected_previous < keepTyping ? expected_previous : keepTyping;min = min < enterNow ? min : enterNow;return min;}void doOperation(int caseNo){vector < int > singleline = getintvectorline();int A_currentTypedCharacters = singleline[0];int B_totalToTypeCharacters = singleline[1];probabilities = getdoublevectorline();double min = expected(A_currentTypedCharacters, B_totalToTypeCharacters);cout << "Case #" << caseNo << ": ";cout.setf(ios::fixed);cout.precision(10);cout << min << endl;}int main(int argc, char * []){DP << "Start testing..." << endl;int numberOfCase = getintline();for ( int i=0; i<numberOfCase; ++i ){doOperation(i+1);}return 0;}
#include <cstdio>#include <iostream>using namespace std;double p[100001];main() {int T, A, B, prob = 1;for (cin >> T; T--;) {cin >> A >> B;for (int i = 0; i < A; i++) cin >> p[i];double ret = 2+B;double pr = 1.0;for (int i = 0; i <= A; i++) {ret = min(ret, pr * ((A-i)+(B-i)+1) + (1-pr) * ((A-i)+(B-i)+1+B+1));pr *= p[i];}printf("Case #%d: %.10lf\n", prob++, ret);}}
large 입력에 대해 속도를 비교해보면.... 처참하다 ㅋ 그래도 나도 문제를 풀었다구!!!! 라고 위안삼아본다.
- Total
- Today
- Yesterday