2020CCPC秦皇島 K 【Kindom's Power】(樹上貪心dp)


前言:感謝博主@crazy_fz 提供的思路。我借鑒的是他的思路,然后大概具體講述一下大佬的思路吧。

原文鏈接:https://www.cnblogs.com/crazyfz/p/13838422.html

題意:給定一顆以1為根的樹,根節點處有無數個人,每一秒只能派一個人移動到他的相鄰節點上,問最少需要多少秒才能使所有結點被訪問過

解法: 我們分析一個樹,發現如果只派出去一個士兵的話,那么實際上我們可以發現一個結論:除了子樹上的最長鏈,其余的各個邊都走了2次。那么在有且只有一個士兵的情況之下,最優(即最小步數)就是把這個最長鏈放到最后去走(這樣就可以減少掉頭回來的重復步數啦!)。那么在之前那個博主所提到的“把子樹按高度從小到大排序”其實就是這個道理:為了避免過度冗余走重復的路徑,我們按照子樹的大小升序排列,就可以最優化走的步驟。但是問題出來了:什么時候發兵,什么時候沿用左邊那棵樹的士兵呢?這個時候我們可以比較一下該點t到左邊那棵樹v的最深點的距離(因為之前說過從小到大排列子樹了)與其到根節點root的距離,如果說從那個最深點到該節點的距離>root到該節點的距離,那么最優的方法應該是從root點派遣新的士兵,反之沿用左邊樹上的士兵。這就是整個題的貪心過程。然后最后答案統計的時候,其實只需要把到每個葉子結點的時間求個sum就行。(因為全程只能動1個士兵,而不是多個士兵同時可以動1格,搞清題意)。

難點分析:因為我自己寫鏈式前向星寫多了,對於子樹排序其實建圖更適用於用vector來建,因為這樣可以對子節點的順序進行排序操作。而鏈式前向星不具備這個特性。此外回溯的時候的dfs寫法也值得深思。

然后最近補題的可以問教練要邀請碼然后去PTA上建重現賽(有效時長是4天)

AC代碼:

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define eps 0.000000001
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const ll inf=0x3f3f3f3f3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e6+5;
vector<pair<int,int> >vec[maxn];//深度,兒子
int getdp(int x){
    if(!vec[x].size()) return 1;
    for(int i=0;i<vec[x].size();i++){
        vec[x][i].first=getdp(vec[x][i].second);
    }
    sort(vec[x].begin(),vec[x].end());
    return vec[x].back().first+1;
}//統計每個點的高度並且按照升序排序
int val[maxn];//從上一個葉子點(或根節點)到該點的最小步數
int dfs(int x,int d,int v){//d統計深度,v代表到該點最小步數
    val[x]=v;
    if(!vec[x].size()) return 1;//回溯過程
    int now=v;
    for(auto it:vec[x]){
        now=min(d,dfs(it.second,d+1,now+1));
    //不斷從左樹往右樹更新now即耗時
        }
    return now+1;
}
int main(){
    int T;scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        int n;scanf("%d",&n);
        rep(i,1,n) vec[i].clear();
        rep(i,2,n){
            int x;scanf("%d",&x);
            vec[x].push_back({0,i});
        }
        getdp(1);
        dfs(1,0,0);
        ll ans=0;
        rep(i,1,n){
            if(!vec[i].size()) ans+=val[i];//把葉子結點的貢獻全部算上
        }
        printf("Case #%d: %lld\n",cas,ans);
    }
    return 0;
}    
/*
1
11
1 2 3 4 4 4 7 4 9 9
*/    
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM