Skip to main content
  1. Posts/
  2. Algorithm/

BOJ 27844 Fertilizing Pastures

·150 words·1 min
Jiho Kim
Author
Jiho Kim
달려 또 달려

📝 문제 정보
#

문제 번역
#

$N$개의 목초지가 있고 ($2 \le N \le 2\cdot 10^5$), $N-1$개의 도로로 연결되어 트리를 형성합니다. 모든 도로를 건너는 데 1초가 걸립니다. 각 목초지는 처음에 풀이 0개이고, $i$번째 목초지의 풀은 초당 $a_i$ ($1\le a_i\le 10^8$) 단위의 속도로 자랍니다. Farmer John은 처음에 목초지 1에 있으며, 모든 목초지의 풀에 비료를 주기 위해 돌아다녀야 합니다. 그가 $x$ 단위의 풀이 있는 목초지를 방문하면, $x$만큼의 비료가 필요합니다. 목초지는 처음 방문할 때만 비료를 주면 되고, 비료를 주는 데는 시간이 걸리지 않습니다.

입력에는 추가 매개변수 $T\in {0,1}$가 포함됩니다.

  • $T=0$이면, Farmer John은 목초지 1에서 끝나야 합니다.
  • $T=1$이면, Farmer John은 아무 목초지에서나 끝낼 수 있습니다.

모든 목초지에 비료를 주는 데 걸리는 최소 시간과 그 시간에 끝내는 데 필요한 최소 비료량을 계산하세요.

입력
#

첫 번째 줄에 $N$과 $T$가 주어집니다.

그 다음 $i$가 $2$부터 $N$까지 각각에 대해, $p_i$와 $a_i$를 포함하는 줄이 주어집니다. 이는 목초지 $p_i$와 $i$를 연결하는 도로가 있다는 의미입니다. $1\le p_i

출력
#

최소 시간과 최소 비료량을 공백으로 구분하여 출력합니다.

🧐 관찰 및 접근
#

  • 일단 $T = 0$인거부터 풀어보자.
    • 시간 자체는 쉽다. 슉슉 들어갔다가 슉슉 나오는걸 트리 DP로 잘 계산하면 된다.
      • 자기 자식들에 대해 해당 서브트리를 처리하는데 걸리는시간 + 2를 더해주면 되는 것 같다.
    • 비료 양은 어떻게되지?
      • 일단 루트 아래에 자식이 두개 있다고 해보자. 당연히 그리디하게 풀이 빨리자라는쪽으로 가야되는데..
      • 하지만 자식쪽이 처리하는데 오래걸린다면, 나중에 가는게 나을수도 있는데..
      • 구두 수선공
        • 이 문제와 비슷한 느낌으로 그리디하게 처리가 될것같다.
    • 그러면 결국 들고있어야 하는 정보가
      • 해당 서브트리를 다 처리하는데 걸리는 시간
      • 해당 서브트리의 풀떼기 가중치의 합
      • 해당 서브트리를 0초에 도착했을때, 해당 서브트리를 처리하는데 드는 비료의 양
      • 이정도면 되나?
  • $T = 1$을 봐보자.
    • 음, 직관적으로 가장 깊이 들어가는 친구 안에서 끝나야한다. 그래야 시간이 최소가 되니까.

💻 풀이
#

  • 코드 (C++):

struct Node{
    // Type 0
    ll time; // 해당 서브트리를 다 처리하는데 걸리는 시간
    ll a_sum; // 해당 서브트리의 a_i의 합
    ll cost; // 해당 서브트리를 다 처리하는데 드는 비용 (0초 시작 가정)

    // Type 1
    ll mxDepth; // 해당 서브트리 내 최대 깊이
    ll cost2; // 해당 서브트리를 다 처리하는데 드는 비용, 해당 서브트리 안의 가장 깊은 노드에서 끝남


    Node(ll time = 0, ll a_sum = 0, ll cost = 0, ll mxDepth = 0, ll cost2 = 0) : time(time), a_sum(a_sum), cost(cost), mxDepth(mxDepth), cost2(cost2) {}
};

vector<int> links[200010];
int a[200010];
Node nodes[200010];

Node dfs(int c){
    if(links[c].empty()){
        nodes[c] = Node(0, a[c], 0, 0, 0);
        return nodes[c];
    }
    vector<Node> children;
    for(auto nxt: links[c]) children.push_back(dfs(nxt));
    sort(all(children), [](Node &x, Node &y){
        return x.a_sum * (y.time+2) > y.a_sum * (x.time+2);
    });

    auto &cur = nodes[c];
    cur.a_sum = a[c];
    for(auto &child: children){
        cur.cost += child.cost + child.a_sum * (cur.time + 1);
        cur.time += child.time + 2;
        cur.a_sum += child.a_sum;
        cur.mxDepth = max(cur.mxDepth, child.mxDepth + 1);
    }

    
    // cost2: 최대깊이 자식을 맨 뒤로 보내면서 계산해보기
    int sz = children.size();
    vector<ll> discover_time(sz, 0), sufSm(sz+1, 0);
    ll totTime = 0;
    rep(i, 0, sz){
        discover_time[i] = totTime + 1;
        totTime += children[i].time + 2;
    }
    rrep(i, 0, sz) sufSm[i] = sufSm[i+1] + children[i].a_sum;

    cur.cost2 = 2e18;
    rep(i, 0, sz){
        auto &child = children[i];
        if(child.mxDepth != cur.mxDepth - 1) continue;
        ll tmp = cur.cost;

        ll child_time = child.time + 2;
        tmp -= child_time * sufSm[i+1]; // 뒤에있는 자식들 땡겨오기
        tmp += child.a_sum * ((cur.time - discover_time[i] + 1) - child_time); // child를 뒤로 보냈으니 그만큼 드는 추가시간
        tmp -= child.cost;
        tmp += child.cost2; // child는 최대깊이 자식이므로 cost2로 계산

        cur.cost2 = min(cur.cost2, tmp);
    }

    return cur;
}

void solve(){
    int N, T; cin >> N >> T;
    rep(i, 2, N+1){
        int p; cin >> p;
        links[p].push_back(i);
        cin >> a[i];
    }

    dfs(1);
    if(T == 0) cout << nodes[1].time << ' ' << nodes[1].cost;
    else cout << nodes[1].time - nodes[1].mxDepth << ' ' << nodes[1].cost2;
}
🔒

구현 코드 잠금

아래 쿠팡 링크를 방문하시면 코드가 공개됩니다.
광고 수익이 블로그 운영에 도움이 됩니다 🙏

🛒 쿠팡 방문하고 코드 보기

방문 후 잠금이 자동으로 해제됩니다