2017년 9월 26일 화요일

(DP - 최적의 순서로 맞추기) 13448 SW 역량 테스트

13448 SW 역량 테스트 https://www.acmicpc.net/problem/13448
Codeforces #436 (Div.2) E - Fire http://codeforces.com/contest/864/problem/E

dp는 최적 부분구조를 이뤄야 시행될 수 있다.
만약 주어지는 입력값이 작으면 bitmask dpset 또는 map에 저장시켜 풀 수 있지만
그 값이 크면 대부분 TLE가 발생한다.
그러므로 최적 부분구조를 만드는것이 중요하다.

1. SW 역량 테스트
T분 동안 진행되는 시험에 N개의 문제가 나온다.
$i$번 문제를 $t$분만에 풀면 $M_{i}-t*P_{i}$점을 얻게된다.
$i$번 문제 푸는 시간은 $R_{i}$이다.
얻을 수 있는 점수의 최댓값을 구하는 문제다.

얼마전에 삼성 SW역량 테스트 A형을 봤는데 생각나게끔 하는 문제다.
사람이 생각보다 많이 왔고 A형이라 매우 쉽게 나왔다.

어쨌든 문제로 돌아가면 주어진 문제들의 정보를 입력 그대로 푼다면 위에서 언급한
bitmask dpset정도를 사용해야한다. (하지만 TLE)

따라서 어떻게든 정렬을 해야하는데 수식을 전개하면 아주 놀라운 일이 벌어진다.
https://www.acmicpc.net/board/view/11824
(수식 전개는 별로어렵지 않으니 직접해보는것 추천)

요약 : $R_{i}/P_{i} < R_{j}/P_{j}$ 이면 $i$를 푼 후 $j$를 푸는것이 낫다.
따라서 정렬한 후에 dp로 풀면된다.
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
struct Save { 
    long long M, P, R; 
    double pivot;
};
int N, T;
Save save[51];
long long dp[51][100001];
long long solve(int idx, long long time) {
    if (idx == N) return 0;
    long long &ret = dp[idx][time];
    if (ret != -1return ret;
    ret = 0;
    if (idx + 1 <= N && time + save[idx].R <= T)
        ret = max(ret, solve(idx + 1, time + save[idx].R) + save[idx].M - (time + save[idx].R)*save[idx].P);
    if (idx + 1 <= N)
        ret = max(ret, solve(idx + 1, time));
    return ret;
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d%d"&N, &T);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].M);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].P);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].R);
    for (int n = 0;n < N;n++) save[n].pivot = save[n].R / (double)save[n].P;
    sort(save, save + N, [](Save const &a, Save const &b)->bool {
        return a.pivot < b.pivot;
    });
    printf("%lld\n", solve(00));
    return 0;
}
cs

2. Codeforces #436 (Div.2) E - Fire
집에 불이 났다.
집에는 값어치가 매겨진 물품들이 있고 그 물품 $i$를 구출하는데 
걸리는 시간 $t_{i}$, 다 타버리는 시간 $d_{i}$, 물품의 가치 $p_{i}$가 주어진다.
구조할 수 있는 물품의 가치의 합을 최대화하도록 만들고 어떤 물품을 구조했는지 구하는 문제다.

이 문제도 최적 부분구조를 만들어야한다.
바로 $d_{i}$를 최소화 하는것부터 구출하면 최적 부분구조를 이룰 수 있다.
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define mp(a,b) make_pair(a,b)
#define mt(a,b,c) mp(a,mp(b,c))
#define mf(a,b,c,d) mp(mp(a,b),mp(c,d))
typedef long long ll;
struct Save {
    int t, d, p;
    int idx;
};
int N;
Save arr[101];
vector<int> ans;
int dp[101][2002];
int solve(int idx, int time) {
    if (idx >= N) return dp[idx][time] = 0;
    int &ret = dp[idx][time];
    if (ret != -1return ret;
    ret = 0;
    if (time + arr[idx].t < arr[idx].d) {
        ret = max(ret, solve(idx + 1, time + arr[idx].t) + arr[idx].p);
    }
    ret = max(ret, solve(idx + 1, time));
    return ret;
}
void reconstruct(int idx, int time) {
    if (idx >= N) return;
    int here = dp[idx][time];
    if (time + arr[idx].t < arr[idx].d && here == dp[idx + 1][time + arr[idx].t] + arr[idx].p) {
        ans.push_back(idx);
        reconstruct(idx + 1, time + arr[idx].t);
        return;
    }
    reconstruct(idx + 1, time);
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d"&N);
    for (int n = 0;n < N;n++scanf("%d%d%d"&arr[n].t, &arr[n].d, &arr[n].p), arr[n].idx = n;
    sort(arr, arr + N, [](Save &a, Save &b)->bool {
        return a.d == b.d ? a.t < b.t : a.d < b.d;
    });
    printf("%d\n", solve(00));
    reconstruct(00);
    printf("%d\n", ans.size());
    for (int &next : ans)printf("%d ", arr[next].idx + 1);
    return 0;
}
cs

댓글 4개:

  1. dp 연습해야 하는데.. ㅠㅠ 유량도 해야 하고..
    맨날 스타 대결 풀어야지.. dp 하나씩 풀어야지 해놓고.
    실천을 못하고 있네요.

    세쌍 서로수를 타일채우기 3이랑 만두 가게 사장 박승원보다 먼저 풀 정도였으니..
    만약에 저 두 문제를 사람들이 많이 안 풀었다면..
    gcd곱을 그 담에 풀었을지도..

    dp는 진짜.. 검은점과 하얀점 연결도 어렵고.. 그냥 어려운 거 같아요..
    저런 비슷한 최적 부분 구조(?) 문제 하나 푼 적이 있긴 한 거 같아요..
    커플 만들기였던 거 같아요..

    답글삭제
  2. 아무튼 저 트릭 연습 좀 하고
    1100번대에 있는 피보나치 냅색도 좀 풀어봐야겠네요..

    답글삭제
    답글
    1. 와 맞아요 dp 너무 어려워요.. 나중에 풀어봐야지 하면서 북마크해놓는데
      북마크가 터질지경이네여ㅋㅋ
      검은점 하얀점 ㅠㅠ...

      삭제
    2. 저도 그거 포함해서 몇 개 더 북마크를 해 두었는데요..
      아직 안 푼 문제가 수두룩합니다..
      dp만 보면 조금만 어려워져도 바로 패스할 정도니..

      심지어 dp 대신에 수학으로 커버가 가능하면
      dp로 푸는 풀이는 과감히 버려버릴 정도입니다.. 1020번이라던지 1023번이라던지..
      그러면 안 되는데.. dp가 식 세우는 게 어렵긴 어렵더라고요..

      삭제