1. 程式人生 > >演算法提高 金屬採集

演算法提高 金屬採集

問題描述
人類在火星上發現了一種新的金屬!這些金屬分佈在一些奇怪的地方,不妨叫它節點好了。一些節點之間有道路相連,所有的節點和道路形成了一棵樹。一共有 n 個節點,這些節點被編號為 1~n 。人類將 k 個機器人送上了火星,目的是採集這些金屬。這些機器人都被送到了一個指定的著落點, S 號節點。每個機器人在著落之後,必須沿著道路行走。當機器人到達一個節點時,它會採集這個節點蘊藏的所有金屬礦。當機器人完成自己的任務之後,可以從任意一個節點返回地球。當然,回到地球的機器人就無法再到火星去了。我們已經提前測量出了每條道路的資訊,包括它的兩個端點 x 和 y,以及通過這條道路需要花費的能量 w 。我們想花費盡量少的能量採集所有節點的金屬,這個任務就交給你了。

輸入格式
第一行包含三個整數 n, S 和 k ,分別代表節點個數、著落點編號,和機器人個數。

接下來一共 n-1 行,每行描述一條道路。一行含有三個整數 x, y 和 w ,代表在 x 號節點和 y 號節點之間有一條道路,通過需要花費 w 個單位的能量。所有道路都可以雙向通行。

輸出格式
輸出一個整數,代表採集所有節點的金屬所需要的最少能量。
樣例輸入
6 1 3
1 2 1
2 3 1
2 4 1000
2 5 1000
1 6 1000
樣例輸出
3004
樣例說明
所有機器人在 1 號節點著陸。

第一個機器人的行走路徑為 1->6 ,在 6 號節點返回地球,花費能量為1000。

第二個機器人的行走路徑為 1->2->3->2->4 ,在 4 號節點返回地球,花費能量為1003。

第一個機器人的行走路徑為 1->2->5 ,在 5 號節點返回地球,花費能量為1001。

資料規模與約定
本題有10個測試點。

對於測試點 1~2 , n <= 10 , k <= 5 。

對於測試點 3 , n <= 100000 , k = 1 。

對於測試點 4 , n <= 1000 , k = 2 。

對於測試點 5~6 , n <= 1000 , k <= 10 。

對於測試點 7~10 , n <= 100000 , k <= 10 。

道路的能量 w 均為不超過 1000 的正整數。

題解:樹形動歸,開始好難理解,先建立一棵樹(孩子連結串列),然後類似dfs遍歷

dp[i][remain]:表示在以i為根的子樹中停留remain個機器人的花費。把一棵子樹看作是一個整體。

一開始,dfs剛到某個節點,如果沒有兒子節點的話,那麼機器人到此就都可以停了,dp[i][j]為0

如果發現了有一個兒子節點,那麼考慮在這個節點(以這個節點為根的子樹)停留(分配)remain個機器人。

dp[i][remain]+=dp[next][0]+cost*2; //子樹son中沒有停留機器人,那麼意味全反回,最少是去一個所以最少反回一個

for(j=1;j<=k;j++) remain中j個結點分配給新的子樹,i結點回去remain-j個結點

dp[i][remain]=min(dp[i][remain],dp[i][remain-j]+dp[next][j]+j*cost);

#include<stdio.h> 
#include<string.h>
#include<stdlib.h>
#define Max_ 100005
#define min(a,b) a<b?a:b
int dp[Max_][12],head[Max_],m=0;//head[]記錄結點所指的下一條邊 ,dp[]記錄當前的最優值 
int n,s,k;

struct Edge{
    int tonode;//記錄這條邊指向的結點 
    int nextedge;//記錄這條邊指向的結點所指向的下一條邊 
    int v;
}edge[Max_*2];

addedge(int fromnode,int tonode,int v1){//將指向tonode結點的那條邊加入邊集 棵樹 
    edge[m].tonode=tonode;//這條邊指向的結點 
    edge[m].nextedge=head[fromnode];//fromnode結點所指向的下一條邊,即,邊edge[m]與邊edge[m].nextedge在此棵樹的同一層, 
    edge[m].v=v1;
    head[fromnode]=m++;// fromnode指向新生成的這條邊 
}

void dfs(int fromnode,int prenode){//類似dfs 
     int i,j,remain;
     for(i=head[fromnode];i!=-1;i=edge[i].nextedge){//head[fromnode]這條邊的同一層進行遍歷 
        int tonode=edge[i].tonode;
        if(tonode==prenode){//此結點已經遍歷過 
              continue;
        }
        dfs(tonode,fromnode);//繼續遍歷edge[i].tonode結點相連的下一條邊,直到出現葉子結點 
        for(remain=k;remain>=0;remain--){//remain指為以fromnode為根結點的子樹分配的機器人個數 
            dp[fromnode][remain]+=dp[tonode][0]+(edge[i].v)*2;//機器人都回該子樹根結點,至少一個機器去遍歷該子樹所有子節點並返回的花費 
            for(j=1;j<=remain;j++){//至少一個機器人進入子樹,j個機器人進入該子樹 
                dp[fromnode][remain]=min(dp[fromnode][remain],dp[fromnode][remain-j]+dp[tonode][j]+j*(edge[i].v));
            } 
        }
     }   
}

int main(){
    memset(dp,0,sizeof(dp));
    int i,fromnode,tonode,v1;
    scanf("%d%d%d",&n,&s,&k);
    for(i=0;i<=n;i++){
        head[i]=-1;// 最開始各節點並沒有指向任何一邊,標記head[i]為-1 
    }
    for(i=1;i<n;i++){
        scanf("%d%d%d",&fromnode,&tonode,&v1);
        //用兩條邊進行表示,則父結點和孩子結點都會在同一層上,兩層中任意相連的兩節點最多隻能取一個 
        addedge(fromnode,tonode,v1);//將指向tonode結點的那條邊加入邊集 
        addedge(tonode,fromnode,v1);//將指向fromnode結點的那條邊加入邊集
    }
    dfs(s,0);
    printf("%d",dp[s][k]);
    return 0;
}