#include <cstring>#include <cstdlib>#include <cstdio>#include <algorithm>#include <iostream>using namespace std;/** Problem: HDU4313 - Matrix - DP Version【0.4%達成】 Copyright : 歸我們學校集訓隊和本人所有,未經同意可以轉載。 【但是膽敢用於培訓的話,貴學校伺服器將經受本人長時間免費義務壓力測試,CC+TCP片段+類比訪問,至少1000 Zombies以上】 【請做好心理準備】 Thanks:感謝提供協助的 Dragon ,zjj , gzm , lqy , zsw,lpp等大神…… = =多謝你們的patient,排名不分先後。。。 Reference:——【凡是給出Reference的,底下的內容就是參考Reference和本人思路寫的,如有錯誤歡迎指正,如不希望本人引用請mail 0daydigger#gmail.com】 http://blog.csdn.net/cyberzhg/article/details/7790486 ——寫的很清晰的代碼,強烈推薦 http://page.renren.com/601081183/note/862977450 ——這個。。其實沒給dp怎麼寫。。。湊合看吧 Knowledge Point:鄰接表格儲存體,樹狀dp 傻逼錯誤:把<看成了>導致理解不能 傻逼錯誤II:媽的居然忘記了鄰接表格儲存體這個事兒!! Thought: 首先,是關於那個奇怪的addEdge,好吧,圖論那章我還沒刷呢。 首先證明addEdge函數能正確的儲存圖的鄰接表。 鄰接表只儲存與節點i相鄰的節點資訊——這句話說給基礎不好的童鞋【就是你自己吧喂! 初始: 初始化的時候,head[]={-1},edge[]中沒有儲存東西,在第一對節點(u,v)被儲存的時候, edge[v].next = head[u] == -1; edge[u].next = head[v] == -1; head[u] = v在edge[]中的下標 head[v] = u在edge[]中的下標 那麼從head[u]和head[v]開始遍曆的話,就能遍曆所有與u和v相鄰的節點。 保持: 對於(u',v'),如果(u',v')都是新節點的話,那麼在【初始】中已經證明了其正確性。 如果有一個不是新節點的話,設u'不是新節點。 那麼【u',v'指的是edge[]中儲存u',v'兩個點的下標,有時候也指節點本身,請按照上下文區分】 head[u']先被更新,指向儲存了v'資訊的edge[]的下標。 然後head[v']更新,儲存了指向u'資訊的edge[]的下標。 由於有一句話 edge[edgeNum].next = head[u'] ... head[u'] = edgeNum++ 那麼edge[v']中保留的就是以前head[v']中的資訊,使得 for(i = head[u]; i != -1 ; i = head[u].next ) 就可以用edge[i]來遍曆u的鄰接表了。 如果兩個節點都是老節點的話,同理 終止 根據【保持】中的分析,得到(u,v)的時候,可以 addEdge(u,v,w); addEdge(v,u,w);便可以使得圖中的每個點都建立起鄰接表了。 ——————————————————————接下來是dp部分的證明———————————————————— dp[][],第一個維度儲存節點,第二個維度只有2個大小 dp[u][0]表示當u為根的子樹(包含u哦,下同)中不含機器時【需要耗費多少時間】 dp[u][1]表示當u為根的子樹中含有一台機器的時候【需要耗費多少時間】 那麼,當u為葉子節點,且u有機器(即color[u] = BLACK)的時候: dp[u][0] = INFINITE; dp[u][1] = 0; u是葉子節點,u不含有機器(color[u] = WHITE)的時候 dp[u][0] = 0; dp[u][1] = INFINITE; 那麼,當u為非葉子節點的時候,對於【全部u的鄰接表中的節點v】(←這句話很重要!閱讀下文的時候請務必牢記!)採取以下策略: ————————————————————嫌囉嗦就直接看最後一部分!—————————————— ①如果u有機器 那沒的說了,dp[u][0]這時候就廢了。只能用dp[u][1]了 dp[u][1] += min(dp[v][0],dp[v][1] + w); //v是u的鄰接表中所有節點 這個方程的意思是說,因為u本身已經有機器了,那麼dp[u][1]只要選 dp[v][0]或者dp[v][1]+w就好,dp[v][1]+w是指把(u,v)這條邊減下去,選這倆小的就好。 ②如果u沒有機器 dp[u][0] += min(dp[v][0],dp[v][1]+w); 還是一樣,如果u沒有機器的話,那麼把dp[v][0]和dp[v][1]+w選一個小的就好,如果v有一個機器的話 只要刪了u與v連著的這條邊就可以了。 但是我們還要考慮dp[u][1]呢。 如果我們選擇了dp[v][0]: 此時只要在消耗的時間中【減去】【最大的】dp[v][1] - dp[v][0]就好 亦即 dp[u][1] = dp[u][0] + minEdge (minEdge = dp[v][1] - dp[v][0],dp[v][1]一定比dp[v][0]要小,因為dp[v][0]要多剪掉一刀) 因為dp[v]中儲存的可都是節點v的最優狀態,我們從鄰接節點v中選出來一條權值最大的帶機器的邊的權值從dp[u][0]中減去 直接得到的就是dp[u][1]的最優解。 如果我們選擇了dp[v][1]+w 此時就是minEdge與 -w 比較,如果minEdge 比較大的話,minEdge = -w; 遍曆中開一個變數cnt記錄u的子樹個數,如果u>0的話,且u沒有機器那麼遍曆結束後: dp[u][1] = dp[u][0] + minEdge( minEdge是負的 ) ————————————給嫌囉嗦的人看—————————— 總之!anyway!我們就是要在節點u的所有後代v中找出來一條權值最大並且串連帶機器的點的邊(臥槽真尼瑪繞嘴) 從dp[u][0]中減去其權值,那麼直接就得到了dp[u][1]的最優解。 具體實現是用DFS實現的(總有種感覺裸搜也能過……) Q.E.D.*/const int WHITE = 0;const int BLACK = 1;const int MAX_SIZE = 100005;const __int64 INFINITE = 100000000000LL;struct node{ __int64 v; __int64 w; int next;};__int64 dp[MAX_SIZE][2];node edge[MAX_SIZE*2];int head[MAX_SIZE];int edgeNum;char color[MAX_SIZE];void addEdge(int u,int v,int w){ edge[edgeNum].v = v; edge[edgeNum].w = w; edge[edgeNum].next = head[u]; head[u] = edgeNum++;}void dfs(int u,int father){ long long minEdge = INFINITE; int cnt = 0; int v = 0; int w = 0; if( color[u] == BLACK ) //has machine { dp[u][0] = INFINITE; dp[u][1] = 0; } else { dp[u][0] = 0; dp[u][1] = INFINITE; } for(int i = head[u]; i != -1 ; i = edge[i].next) { v = edge[i].v; w = edge[i].w; if( v != father ) { dfs(v,u); if( color[u] ) { dp[u][1] += min(dp[v][0],dp[v][1] + w); } else { dp[u][0] += min(dp[v][0],dp[v][1] + w ); cnt++; if ( dp[v][0] <= dp[v][1] + w) { if( minEdge > dp[v][1] - dp[v][0] ) minEdge = dp[v][1] - dp[v][0]; } else { if( minEdge > -w ) minEdge = -w; } } } } if( color[u] == WHITE && cnt > 0 ) dp[u][1] = dp[u][0] + minEdge;}int main(){ int T; int N,K; int u,v,w;#ifndef ONLINE_JUDGE freopen("B:\\acm\\SummerVacation\\DP-II\\C.in","r",stdin); freopen("B:\\acm\\SummerVacation\\DP-II\\C.out","w",stdout);#endif while(scanf("%d",&T) != EOF) { for(int t = 1 ; t <= T ; t++) { memset(head,-1,sizeof(head)); memset(color,0,sizeof(color)); edgeNum = 0; memset(edge,0,sizeof(edge)); scanf("%d%d",&N,&K); for(int i = 1 ; i < N ; i++) { scanf("%d%d%d",&u,&v,&w); addEdge(u,v,w); addEdge(v,u,w); //構造串連表 } for(int i = 0 ; i < K ; i++) { scanf("%d",&u); color[u] = BLACK; } dfs(0,-1); if( color[0] == WHITE ) { printf("%I64d\n",min(dp[0][0],dp[0][1])); } else { printf("%I64d\n",dp[0][1]); } } }#ifndef ONLINE_JUDGE fclose(stdin); fclose(stdout);#endif return 0;}