#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.youkuaiyun.com/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;
}