pku 1849 two 解题报告
题目链接: http://acm.pku.edu.cn/JudgeOnline/problem?id=1849
题意:
给出一棵树的公路网,公路网堆满雪,需要2辆汽车扫完全部雪,树的权值表示耗费汽车的油费,求汽车从结点S开始,用最少汽油费扫完全部雪,汽油费是多少?
思路:
通过此题,更加深刻了解树形dp的特点,树形dp通常是典型的从低向上dp的,解决数据大,是树形结构的。解决问题的关键在于寻找其状态方程,然后用深度搜索搜到树低,然后利用状态转移方程一步一步往上dp。
由于题目只是要求2辆汽车,那么我们想想,如果S只有一棵子树,出现的情况可能就是1辆汽车走到底或者2辆汽车走到底.再深入一步想,有两棵子树呢?就应该2辆汽车各走一个子树。3棵子树呢?就应该有1可子树是最短的而且走2遍,以此类推4、5、6```棵子树的情况,只要先求出最短2棵子树的路径,那么所有子树的路径之和*2-2棵最短路径子树之和即为答案.那么状态转移方程就出来了: j为i的儿子
dp[i][1]=2*j->Distance+dp[j][1] //1辆汽车去一棵子树了又返回其结点
dp[i][2]= min(dp[i][1]+ dp[j][2] - dp[j][1] - j->Distance) //1辆汽车去了子树不返回其结点
dp[i][3]=min(dp[i][1] +dp[j][3] - dp[j][1]- LongestPath - MinisterPath)//2辆汽车去了子树都不返回其结点
AC代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define M 100005
typedef struct node
{
int Point, Distance;
struct node *next;
}*adjv;
adjv tree[M];
int N, S;
bool visit[M]; //对结点的访问
int dp[M][3]; //只需要记录三条路径 1:1--->1(1人去同时此人回到原点), 2:1---->0(1人去同时此人不回来), 3:2---->0(2人去同时2人都不回来)
void input()
{
int i, a, b, c;
for (i = 1; i <= N; i++)
{
tree[i] = NULL;
}
for (i = 1; i < N; i++)
{
//用链接表保存这棵树
scanf("%d%d%d", &a, &b, &c);
node *temp = new node;
temp->Point = b; temp->Distance = c; temp->next = NULL;
if (tree[a] == NULL)
{
tree[a] = temp;
}
else
{
node *tmp = tree[a];
while (tmp->next)
{
tmp = tmp->next;
}
tmp->next = temp;
}
temp = new node;
temp->Point = a; temp->Distance = c; temp->next = NULL;
if (tree[b] == NULL)
{
tree[b] = temp;
}
else
{
node *tmp = tree[b];
while (tmp->next)
{
tmp = tmp->next;
}
tmp->next = temp;
}
}
}
// dp方程是dp[k][i][j]表示以第k个点为子树的去了i个人回来j个人的最小花费
// 这样需要对3组i,j的值进行计算,不难看出是1 0, 1 1, 2 0,
void dfs(int point)
{
visit[point] = true;
int SavePath[M];
node *temp = tree[point];
//一人去一人返回
dp[point][1] = 0;
int i, min1 = 0, min2 = 0, count = 0;
while (temp)
{
if (!visit[temp->Point])
{
//深度搜索搜索到树低
dfs(temp->Point);
//1----->1 路径*2+儿子回来的路径
dp[point][1] += 2 * temp->Distance + dp[temp->Point][1];
//画图可知,求出point点1人回来与1人不回来的差值
if (min1 > dp[temp->Point][2] - dp[temp->Point][1] - temp->Distance)
{
min1 = dp[temp->Point][2] - dp[temp->Point][1] - temp->Distance;
}
//画图可知,求出point点2人回来与2人不回来的差值
if (min2 > dp[temp->Point][3] - dp[temp->Point][1])
{
min2 = dp[temp->Point][3] - dp[temp->Point][1];
}
SavePath[count++] = dp[temp->Point][2] - dp[temp->Point][1] - temp->Distance;
}
temp = temp->next;
}
//1人不回来的数值
dp[point][2] = dp[point][1] + min1;
int LongestPath = 0, MinisterPath = 0;
//如果儿子大于2的话,即子树>=2,那么最佳路径就是所有子树路径*2-2条最长路径,而2人也不用回来.
if (count > 1)
{
for (i = 0; i < count; i++)
{
if (SavePath[i] < LongestPath)
{
MinisterPath = LongestPath;
LongestPath = SavePath[i];
}
else if (SavePath[i] < MinisterPath)
{
MinisterPath = SavePath[i];
}
}
min2 = ((LongestPath + MinisterPath) < min2 ? (LongestPath + MinisterPath) : min2);
}
dp[point][3] = dp[point][1] + min2;
}
int min(int a, int b, int c)
{
int temp = a > b ? b : a;
return temp > c ? c : temp;
}
int main()
{
//freopen("1.txt", "r", stdin);
scanf("%d%d", &N, &S);
input();
dfs(S);
//答案取3种情况的最优值
int ans = min(dp[S][1], dp[S][2], dp[S][3]);
printf("%d/n", ans);
return 0;
}