ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2022.07.18. 복습 1 풀이
    SHARC - 2022/SHARC - 수업 (2022 3분기) 2022. 7. 21. 18:27

    다뤘던 태그들

    더보기
    • Brute Force, Dynamic Programming, Greedy
    • Sort
    • Stack, Queue, Heap (Priority Queue)
    • Exchange Argument
    • Prefix Sum
    • Binary Search, Parametric Search
    • Greatest Common Divisor, Least Common Multiple, (Euclidean Algorithm)
    • Multidimensional DP, Interval DP, Knapsack, Longest Increasing Subsequence, Longest Common Substring
    • Recursion, Backtracking, Divide and Conquer
    • Bitmasking
    • Coordinate Compression
    • Game Theory

    + 다루려다가 안 넣은 태그들

    • Deque
    • Ternary Search

    A. [2798] 블랙잭 (Bronze II)

    더보기

    의도한 태그: BruteForce

    카드 3장을 고르는 경우의 수를 일일이 다 돌려주면 됩니다.

    int arr[120];
    
    void Main(){
    	int n, m; cin >> n >> m;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	int ans = 0;
    	for (int i = 1; i <= n; i++){
    		for (int j = i+1; j <= n; j++){
    			for (int k = j+1; k <= n; k++){
    				int res = arr[i]+arr[j]+arr[k];
    				if (res <= m){ ans = max(ans, res); }
    			}
    		}
    	}
    	cout << ans;
    }

    B. [11723] 집합 (Silver V)

    더보기

    의도한 태그: Bitmask

    원소가 1 ~ 20이니, 각각 하나의 비트로 저장시켜도 20비트밖에 들지 않습니다.

    일반적인 정수형에 넣을 수 있을 정도로 작은 값이죠.

    void Main(){
    	int bit = 0;
    	int q; cin >> q; while (q--){
    		string s; cin >> s;
    		if (s == "add"){ int x; cin >> x; bit |= 1<<x; }
    		if (s == "remove"){ int x; cin >> x; bit &= ~(1<<x); }
    		if (s == "check"){ int x; cin >> x; cout << (bit>>x & 1) << endl; }
    		if (s == "toggle"){ int x; cin >> x; bit ^= 1<<x; }
    		if (s == "all"){ bit = 0xfffff << 1; }
    		if (s == "empty"){ bit = 0x00000 << 1; }
    	}
    }

    C. [11866] 요세푸스 문제 0 (Silver V)

    더보기

    의도한 태그: Queue

    우선 큐에 1부터 N까지 다 넣은 뒤, 아래 과정을 N번 반복해주면 됩니다.

    • K-1회 동안, 큐의 맨 앞의 원소를 맨 뒤로 옮긴다.
    • 그 뒤, 큐의 맨 앞의 원소를 출력하고 제거한다.
    void Main(){
    	int n, k; cin >> n >> k;
    	queue<int> q;
    	for (int i = 1; i <= n; i++){ q.push(i); }
    	cout << "<"; while (n--){
    		for (int i = 1; i < k; i++){ q.push( q.front() ); q.pop(); }
    		cout << q.front(); q.pop();
    		if (n){ cout << ", "; } else{ cout << ">"; }
    	}
    }

    D. [10814] 나이순 정렬 (Silver V)

    더보기

    의도한 태그: Sort

    Stable Sort를 직접 구현해줘도 되고, (나이, 가입 순서, 이름) 순서대로 정렬시켜도 됩니다.

    저의 경우는 후자를 택했습니다.

    psi2 arr[100020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i].sc.fr >> arr[i].fr; arr[i].sc.sc = i; }
    	sort(arr+1, arr+n+1, [](psi2 p1, psi2 p2){ return p1.sc < p2.sc; });
    	for (int i = 1; i <= n; i++){ cout << arr[i].sc.fr << ' ' << arr[i].fr << endl; }
    }

    E. [25344] 행성 정렬 (Silver IV)

    더보기

    의도한 태그: LCM

    첫 3개의 행성은 \( T_{1} \)초마다 정렬됩니다.

    첫 4개의 행성은 \( \text{lcm}(T_{1}, T_{2}) \)초마다 정렬됩니다.

    첫 5개의 행성은 \( \text{lcm}(T_{1}, T_{2}, T_{3}) \)초마다 정렬됩니다.

    ...

    첫 N개의 행성은 \( \text{lcm}(T_{1}, T_{2}, T_{3}, \ldots, T_{N-2}) \)초마다 정렬됩니다.

    void Main(){
    	ll ans = 1;
    	int n; cin >> n; n -= 2; while (n--){
    		ll x; cin >> x; ans = lcm(ans, x);
    	}
    	cout << ans;
    }

    F. [2217] 로프 (Silver IV)

    더보기

    의도한 태그: Greedy

    로프의 개수를 \( c \)개로 고정시켜봅시다.

    그럼, 자명하게 가장 강한 \( c \)개의 로프를 써야 합니다.

    만약 \( A_{1} \ge A_{2} \ge \cdots \ge A_{N} \)으로 정렬이 되어있다면, 가장 강한 \( c \)개의 로프는 첫 \( c \)개의 로프가 되고, 이 때 들 수 있는 최대 강도는 \( c \times \min(A_{1}, A_{2}, \ldots, A_{c}) = c \times A_{c} \)가 됩니다.

    그러니, 가능한 로프의 개수를 다 돌려보면서 \( \max(i \times A_{i}) \)를 구해주면 됩니다.

    int arr[100020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	sort(arr+1, arr+n+1, [](int a, int b){ return a > b; });
    	ll ans = 0;
    	for (int i = 1; i <= n; i++){
    		ll res = (ll)arr[i] * i;
    		ans = max(ans, res);
    	}
    	cout << ans;
    }

    G. [1026] 보물 (Silver IV)

    더보기

    의도한 태그: Exchange Argument

    만약 \( a > b, x > y \)라고 해봅시다.

    그렇다면, \( (b-a)(y-x) > 0 \), 즉 \( by - bx - ay + ax > 0 \), \( by + ax > bx + ay \)가 됩니다,

    이는 (큰 수 × 큰 수) 와 (작은 수 × 작은 수)가 있다면, (큰 수 × 작은 수)와 (작은 수 × 큰 수)로 놓는 것이 더 이익임을 보여줍니다.

    그러니 한 쪽은 정렬하고, 다른 한 쪽은 역순정렬해서 매치해주면 됩니다.

     

    참고로 위 식은 하도 유명해서 Rearrangement Inequality라고 이름이 붙었으며, 최소화와 최대화를 둘 다 다룹니다.

    int arr[60], brr[60];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; } sort(arr+1, arr+n+1);
    	for (int i = 1; i <= n; i++){ cin >> brr[i]; } sort(brr+1, brr+n+1, [](int a, int b){ return a > b; });
    	ll ans = 0; for (int i = 1; i <= n; i++){ ans += arr[i]*brr[i]; }
    	cout << ans;
    }

    H. [1920] 수 찾기 (Silver IV)

    더보기

    의도한 태그: Binary Search

    수열 A를 정렬시킨 뒤, 입력으로 x를 받을 때마다 이진 탐색으로 빠르게 찾아주면 됩니다.

    int arr[100020];
    
    void Main(){
    	int n; cin >> n; for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	sort(arr+1, arr+n+1);
    	int q; cin >> q; while (q--){
    		int x; cin >> x;
    		int idx = lower_bound(arr+1, arr+n+1, x) - arr;
    		cout << (idx <= n && arr[idx] == x) <<endl;
    	}
    }

    I. [6555] The Sierpinski Fractal (Silver III)

    더보기

    의도한 태그: Recursion

    N = 1일 때는 그냥 삼각형을 출력해주면 됩니다.

    그렇지 않을 때는, 아래 방식을 참고해서 재귀적으로 삼각형을 출력해주면 됩니다.

    • \( i \)번째 단계의 삼각형은 가로 \( 2^{i+1} \), 세로 \( 2^{i} \)의 길이를 가지고 있다.
    • \( i+1 \)번째 단계의 삼각형은 \( i \)번째 단계의 삼각형 3개로 이루어져 있으며...
      • 좌측 하단의 삼각형은 원래 위치보다 \( 2^{i} \)만큼 밑으로 내려와있다.
      • 우측 하단의 삼각형은 좌측 하단의 삼각형보다 \( 2^{i+1} \)만큼 오른쪽으로 와있다.
      • 상단의 삼각형은 원래 위치보다 \( 2^{i+1} / 2 = 2^{i} \)만큼 오른쪽으로 와있다.
    char res[2050][4100];
    void f(int y, int x, int n){
    	//cout << "F " << y << ' ' << x << ' ' << n << endl << flush;
    	if (n == 1){
    		res[y][x+1] = '/'; res[y][x+2] = '\\';
    		res[y+1][x] = '/'; res[y+1][x+1] = res[y+1][x+2] = '_'; res[y+1][x+3] = '\\';
    		return;
    	}
    	int m = 1 << n-1;
    	f(y, x+m, n-1);
    	f(y+m, x, n-1); f(y+m, x+2*m, n-1);
    }
    
    void Main(){
    	while (1){
    		memset(res, ' ', sizeof(res));
    		int n; cin >> n; if (n == 0){ return; }
    		f(1, 1, n); int m = 1<<n;
    		for (int i = 1; i <= m; i++){
    			for (int j = 1; j <= m+i; j++){ cout << res[i][j]; }
    			cout << endl;
    		}
    		cout << endl;
    	}
    }

    J. [2559] 수열 (Silver III)

    더보기

    의도한 태그: Prefix Sum

    길이가 \( k \)인 구간의 합을 누적합을 사용해서 빠르게 구해주면 됩니다.

    int arr[100020]; ll prf[100020];
    inline ll qry(int st, int ed){ return prf[ed] - prf[st-1]; }
    
    void Main(){
    	int n, m; cin >> n >> m;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	for (int i = 1; i <= n; i++){ prf[i] = prf[i-1] + arr[i]; }
    	ll ans = -1e15;
    	for (int st = 1; st <= n; st++){
    		int ed = st + m-1; if (ed > n){ break; }
    		ll res = qry(st, ed); ans = max(ans, res);
    	}
    	cout << ans;
    }

    K. [10799] 쇠막대기 (Silver III)

    더보기

    의도한 태그: Stack?

    우선 입력받은 문자열을 약간 변형시켜서, 레이저를 따로 '|'라는 캐릭터로 만들어봅시다.

     

    그러면, 여는 괄호가 나올 때마다 막대기가 하나 추가되고,

    닫는 괄호가 나올 때마다 막대기가 하나 빠지며 (이 막대기도 세야 하니 답에 1이 더해집니다),

    레이저가 나올 때마다 답에 현재 있는 막대기의 개수만큼 추가됩니다.

    void Main(){
    	string s; cin >> s; int sl = s.size();
    	string tmp = ""; int tl = 0; for (int i = 0; i < sl; i++){
    		if (s[i] == '('){ tmp += "("; tl += 1; }
    		if (s[i] == ')'){
    			if (s[i-1] == '('){ tmp[tl-1] = '|'; }
    			else{ tmp += ")"; tl += 1; }
    		}
    	}
    	s = tmp; sl = tl;
    	ll ans = 0; int cnt = 0;
    	for (int i = 0; i < sl; i++){
    		if (s[i] == '('){ cnt += 1; }
    		if (s[i] == ')'){ ans += 1; cnt -= 1; }
    		if (s[i] == '|'){ ans += cnt; }
    	}
    	cout << ans;
    }

    L. [11727] 2×n 타일링 2 (Silver III)

    더보기

    의도한 태그: Dynamic Programming

    우선 \( D_{i} = 2 \times i \)의 타일링을 하는 경우의 수 를 정의해봅시다.

    그럼, 마지막 줄에 무슨 타일을 놓느냐에 따라 아래 3가지 경우가 나오게 됩니다.

    • 2x1를 세로로 놓는 경우: \( 2 \times (i-1) \)만큼의 타일링을 더 해야 합니다. \( D_{i-1} \)가지의 경우가 있습니다.
    • 2x1을 가로로 놓는 경우: 가로로 2개를 위아래로 놓는 경우밖에 없으며, 이 때는 \( 2 \times (i-2) \)만큼의 타일링을 더 해야 합니다. \( D_{i-2} \)가지의 경우가 있습니다.
    • 2x2를 놓는 경우: \( 2 \times (i-2) \)만큼의 타일링을 더 해야 합니다. \( D_{i-2} \)가지의 경우가 있습니다.

    이를 다 합하면, \( D_{i} = D_{i-1} + 2 D_{i-2} \)라는 점화식이 나오게 됩니다.

    초항인 \( D_{0} = 1, D_{1} = 1 \)도 구해준다면, 문제를 풀 수 있습니다.

    const int mod = 10007;
    int dp[1020];
    
    void Main(){
    	int n; cin >> n;
    	dp[0] = 1; dp[1] = 1;
    	for (int i = 2; i <= n; i++){ dp[i] = dp[i-1] + 2*dp[i-2]; dp[i] %= mod; }
    	cout << dp[n];
    }

    M. [18870] 좌표 압축 (Silver II)

    더보기

    의도한 태그: Coordinate Compression

    입력받은 값들을 모두 set에 넣은 뒤, 순서대로 꺼내면서 0부터 번호를 매겨줍니다.

    그 뒤, 입력받았던 값들에 매겨진 번호를 하나하나 출력하면 됩니다.

     

    (또는 sort로 정렬한 뒤 map에 넣는 방식도 있고, sort한 뒤 이진 탐색으로 인덱스를 찾는 방법도 있습니다.)

    int arr[1000020];
    map<int, int> cvt; set<int> cs; int cc;
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; cs.insert(arr[i]); }
    	for (int x : cs){ cvt[x] = cc; cc += 1; }
    	for (int i = 1; i <= n; i++){ cout << cvt[ arr[i] ] << ' '; }
    }

    N. [1182] 부분수열의 합 (Silver II)

    더보기

    의도한 태그: Backtracking

    부분수열을 고르는 경우의 수는 $ O(2^{N}) $가지 경우가 있습니다.

    그러니 가능한 모든 경우를 백트래킹으로 다 탐색해주면 됩니다.

    S가 0일 때는 아무것도 선택하지 않는 경우 1번을 빼야 함에 주의하세요.

    O. [15903] 카드 합체 놀이 (Silver I)

    더보기

    의도한 태그: Heap (Priority Queue)

    매 순간, 가장 작은 두 카드를 골라서 합체하는 게 최적입니다.

    가장 작은 카드는 pq 등을 사용해서 빠르게 찾아주면 됩니다.

    void Main(){
    	int n, m; cin >> n >> m;
    	priority_stack<ll> pq; while (n--){ ll x; cin >> x; pq.push(x); }
    	while (m--){
    		ll a = pq.top(); pq.pop(); ll b = pq.top(); pq.pop();
    		pq.push(a+b); pq.push(a+b);
    	}
    	ll ans = 0;
    	while (!pq.empty()){ ans += pq.top(); pq.pop(); }
    	cout << ans;
    }

    P. [1932] 정수 삼각형 (Silver I)

    더보기

    의도한 태그: Multidimensional DP

    \( D_{i, j} = (i, j) \)에서 출발했을 때 얻을 수 있는 최대 점수 라고 합시다.

    그럼 \( i = N \)일 때는 \( D_{N, j} = A_{N, j} \)가 되고,

    그렇지 않을 때는 \( D_{i, j} = \max(D_{i+1, j}, D_{i+1, j+1}) + A_{i, j} \)가 됩니다.

    (각각 왼쪽, 오른쪽으로 내려갈 때입니다.)

    int arr[520][520]; int dp[520][520];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){
    		for (int j = 1; j <= i; j++){ cin >> arr[i][j]; }
    	}
    	for (int i = n; i >= 1; i--){
    		for (int j = 1; j <= n; j++){ dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + arr[i][j]; }
    	}
    	cout << dp[1][1];
    }

    Q. [25343] 최장 최장 증가 부분 수열 (Gold V)

    더보기

    의도한 태그: Longest Increasing Subsequence

    \( D_{i, j} = (i, j) \)를 끝점으로 하는 LIS의 길이 를 정의해봅시다.

    그러면, \( D_{i, j} = \max_{y \le i, x \le j, A_{y,x} < A_{i,j}}(D_{y, x}) + 1 \)라는 점화식을 찾을 수 있습니다.

    int arr[120][120], dp[120][120];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){
    		for (int j = 1; j <= n; j++){ cin >> arr[i][j]; }
    	}
    	int ans = 0;
    	for (int i = 1; i <= n; i++){
    		for (int j = 1; j <= n; j++){
    			int mx = 0;
    			for (int y = 1; y <= i; y++){
    				for (int x = 1; x <= j; x++){
    					if (arr[y][x] < arr[i][j]){ mx = max(mx, dp[y][x]); }
    				}
    			}
    			dp[i][j] = mx+1; ans = max(ans, dp[i][j]);
    		}
    	}
    	cout << ans;
    }

    R. [21870] 시철이가 사랑한 GCD (Gold V)

    더보기

    의도한 태그: Divide and Conquer

    문제에 있는 대로 구현해주면 됩니다. :blobthinking:

    마스터 정리에 의해, \( T(N) = 2 T(N/2) + O(N) \)인데, 이는 \( O(N \log N) \) 시간이기 때문입니다.

    int arr[200020];
    int dnc(int st, int ed){
    	if (st == ed){ return arr[st]; }
    	int mid = (st+ed-1) >> 1;
    	int ql = dnc(st, mid), qr = dnc(mid+1, ed);
    	int gl = arr[st], gr = arr[ed];
    	for (int i = st; i <= ed; i++){
    		if (i <= mid){ gl = gcd(gl, arr[i]); } else{ gr = gcd(gr, arr[i]); }
    	}
    	return max(ql+gr, gl+qr);
    }
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	cout << dnc(1, n);
    }

    S. [9660] 돌 게임 6 (Gold V)

    더보기

    의도한 태그: Game Theory

    우선, N = 0일 때는 자명히 패배 상태입니다.

    N = 1일 때는 1개를 빼가면 N = 0이 되니 승리 상태입니다.

    N = 2일 때는 1개를 빼가도 N = 1이 되니 패배 상태입니다.

    N = 3일 때는 3개를 빼가면 N = 0이 되니 승리 상태입니다.

    N = 4일 때는 4개를 빼가면 N = 0이 되니 승리 상태입니다.

    N = 5일 때는 3개를 빼가면 N = 2가 되니 승리 상태입니다.

    N = 6일 때는 4개를 빼가면 N = 2가 되니 승리 상태입니다.

    승리 상태가 4번 연속 지속되었으니, 이 다음 7개도 위와 같은 패턴을 가지게 됩니다.

    그리고 그 패턴이 계속 반복될테니, 7로 나눈 나머지가 0 또는 2라면 창영이가, 아니면 상근이가 이깁니다.

    void Main(){
    	ll n; cin >> n;
    	cout << (n%7==0 || n%7==2 ? "CY" : "SK");
    }

    T. [11056] 두 부분 문자열 (Gold IV)

    더보기

    의도한 태그: Longest Common Substring

    A와 B를 부분 문자열로 가지는 가장 짧은 S는, A와 B에서 동시에 등장하는 부분이 많이 겹쳐야 합니다.

    그러니, LCS를 다 겹치게 만들어주면 됩니다.

    문제의 답은 \( |A| + |B| - |LCS| \)입니다.

    int dp[1020][1020];
    
    void Main(){
    	string s1, s2; cin >> s1 >> s2; int n = s1.size(), m = s2.size();
    	s1 = " " + s1; s2 = " " + s2;
    	for (int i = 1; i <= n; i++){
    		for (int j = 1; j <= m; j++){
    			dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
    			if (s1[i] == s2[j]){ dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1); }
    		}
    	}
    	cout << n+m - dp[n][m];
    }

    U. [2981] 검문 (Gold IV)

    더보기

    의도한 태그: Greatest Common Divisor

    두 인접한 수의 차들로 GCD를 계산해주면 됩니다.

     

    그렇게 해서 나온 값의 약수들을 O(sqrt N) 시간에 구해주면 됩니다.

    int arr[120];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	ll g = abs(arr[2]-arr[1]);
    	for (int i = 1; i < n; i++){ g = gcd(g, abs(arr[i+1]-arr[i])); }
    	vector<int> ans;
    	for (ll p = 2; p*p <= g; p++){
    		if (g%p == 0){
    			ans.push_back(p);
    			if (g/p != p){ ans.push_back(g/p); }
    		}
    	}
    	ans.push_back(g);
    	sort(all(ans)); for (int p : ans){ cout << p << ' '; }
    }

    V. [1300] K번째 수 (Gold II)

    더보기

    의도한 태그: Parametric Search

    정답을 기준으로 이진 탐색을 해봅시다.

     

    현재 보고 있는 수가 \( X \)일 때, \( X \) 이하의 수는 몇 개 있는가? 라는 문제를 해결하면 됩니다.

    이는 각 세로줄에 대하여, \( i \)번째 세로줄에는 \( i \)의 첫 \( N \)개 배수들이 있음을 사용하면 아래와 같은 식을 만들 수 있습니다. (\( N \)개밖에 없기 때문에 \( N \)으로 상한을 놓아야 합니다!)

    \[ f(X) = \sum_{i=1}^{N} \min(X/i, N) \]

     

    그러니 \( f(x) \le k \)인 최소의 \( x \)를 파라메트릭 서치로 찾아주면 됩니다.

    void Main(){
    	int n; ll k; cin >> n >> k;
    	ll st = 1, ed = (ll)n*n, ans = 1;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll cnt = 0;
    		for (int i = 1; i <= n; i++){ cnt += min((ll)n, mid/i); }
    		if (cnt < k){ st = mid+1; } else{ ed = mid-1; ans = mid; }
    	}
    	cout << ans;
    }

    W. [1509] 팰린드롬 분할 (Gold I)

    더보기

    의도한 태그: Interval DP

    \( P_{i, j} = S_{i}S_{i+1} \ldots S_{j} \)가 팰린드롬인가? 를 정의하면,

    \[ P_{i, j} = \begin{cases} \text{True} & \text{if } i = j \\ S_{i} = S_{j} & \text{if } i+1 = j \\ S_{i} = S_{j} \text{ and } P_{i+1, j-1} & \text{otherwise} \end{cases} \]

    가 됩니다.

     

    이제, \( D_{i} = S_{1}S_{2} \ldots S_{i} \)를 분할하는데 필요한 팰린드롬의 최소 개수 를 정의하면,

    \( D_{i} = \min_{j < i, P_{j+1, i} = \text{True}}(D_{j}) + 1 \)가 됩니다.

    bool pal[2520][2520]; int dp[2520];
    
    void Main(){
    	string s; cin >> s; int n = s.size(); s = " " + s;
    	for (int i = 1; i <= n; i++){ pal[i][i] = 1; }
    	for (int i = 1; i < n; i++){ pal[i][i+1] = (s[i] == s[i+1]); }
    	for (int l = 3; l <= n; l++){
    		for (int i = 1; i <= n; i++){
    			int j = i + l-1; if (j > n){ break; }
    			pal[i][j] = (s[i] == s[j] && pal[i+1][j-1]);
    		}
    	}
    	for (int i = 1; i <= n; i++){
    		int mn = 1e9;
    		for (int j = 0; j < i; j++){
    			if (pal[j+1][i]){ mn = min(mn, dp[j]); }
    		}
    		dp[i] = mn+1;
    	}
    	cout << dp[n];
    }

    X. [10982] 행운쿠키 제작소 (Platinum IV)

    더보기

    의도한 태그: Knapsack

    \( D_{a} = \) 1번 오븐에서 \( a \)초 동안 쿠키를 구울 때, 2번 오븐으로 나머지 쿠키를 굽는데 걸리는 최소 시간 이라고 정의해봅시다.

     

    그럼, 어떤 쿠키가 \( (a, b) \)의 시간이 든다고 할 때, 아래 방식으로 점화식을 만들 수 있습니다.

    • 1번 오븐에서 굽기: \( D_{i} = D_{i-a} \)
    • 2번 오븐에서 굽기: \( D_{i} = D_{i}+b \)

    이제 이 점화식을 각각의 쿠키에 대해서 진행해주면 됩니다.

    const int N = 100000;
    int dp[100020];
    
    void Main(){
    	int t; cin >> t; while (t--){
    		int n; cin >> n; memset(dp, 0, sizeof(dp));
    		while (n--){
    			int a, b; cin >> a >> b;
    			for (int i = N; i >= 0; i--){
    				if (i >= a){ dp[i] = min(dp[i-a], dp[i]+b); }
    				else{ dp[i] = dp[i]+b; }
    			}
    		}
    		int ans = 1e9;
    		for (int i = 0; i <= N; i++){ ans = min( ans, max(i, dp[i]) ); }
    		cout << ans << endl;
    	}
    }
Designed by Tistory.