hdu 4340 树形dp+染色问题

来源:互联网 发布:信捷触摸屏怎么编程 编辑:程序博客网 时间:2024/05/01 09:54

题目大意:

N个点,用两种颜色标记,标记每个点都要付出一定的花费,且如果某一个点被标记,与它相邻的点的标记同样颜色的花费变为之前的二分之一,求出标记所有点所需要的最小花费。

输入:

第一行输入N

第二行输入第一种颜色标记每个点的花费A[i]

第二行输入第二种颜色标记每个点的花费B[i]

接下来是每对相邻的城市j

输出:

输出标记的最小化费

Sample input

3

1 2 5

3 8 1

1 2

1 3

Sample output

3

题目分析:

最优解问题,一般都可以考虑动态规划的解法。

动态规划问题最重要的就是确定状态,这道题呢,而且求最小花费,和花费相关的状态就是当前点要染成的颜色和与它相邻的点染成的染色,以及与它相邻的颜色是否被染色有关。

那么我就可以这样定义dp数组:

Dp[vertrix][2][2]

第一维是记录当前点的标号;

第二维是记录当前点所染的颜色

第三维记录的是当前点在被染色前,当前点的染色起点是否在子树中。

这样看,因为是找相邻,所以dp并非线性的,那么我们可以通过深度优先搜索做一棵搜索树,转换成树形结构进行动态规划。

确定dp的初始状态,也就是叶节点,对于叶节点,叶节点可以是自身是染色的起点或者不是两种情况,那么

for ( int i = 0 ; i < 2 ; i++ )

            dp[u][i][0] = cost[u][i]/2 , dp[u][i][1] = cost[u][i];

我们设置两种颜色是该颜色染色起点的花费是原花费,不是染色起点的花费是原花费的一半。在回溯时,对于每种颜色做同样的处理:

 int sum = 0 , dif = INF;

        int cst = cost[u][i];

        for ( int j = 0 ; j < e[u].size() ; j++ )

        {

            int v = e[u][j];

            if ( v == fa ) continue;

            int temp = min ( dp[v][i][0] , dp[v][i^1][1] );

            sum += temp;

            dif = min ( dif , dp[v][i][1] - temp );

        }

        dp[u][i][0] = cst/2 + sum;

        dp[u][i][1] = min ( cst + sum , cst/2 + sum + dif );

    }

逐步拆解分析:

首先设当前节点为,它的儿子节点为集合V中的v

所以对于dp[u][颜色][0],它是当前节点为根的子树中没有该点染成该种颜色染色起点的最小花费,那么因为没有该种颜色的染色起点,所以该子树根节点也一定不是染色起点,那么该点处染色的花费一定是折半的,而且为了保持最优,因为转移的花费固定,肯定是从可能转移到该种状态里最优的选择,要满足能转移到这种状态,要满足前提条件:就是当前点的儿子要么以该儿子节点染成相同的颜色,且它的子树中也不存在它的染色起点,要么是不同颜色的染色起点在子树中(如果染色起点不在子树中,且其父亲颜色不同,那么该点的染色不存在起点,矛盾!)的情况,每个孩子都在两种情况中取最优的,再加上转移花费,就可以得到dp[u][颜色][0]的最优情况。

对于dp[u][颜色][1],它是当前节点染成该种颜色,且染色起点在该节点为根节点的子树中的情况,那么能到转移到它的子情况:

一、他自身是染色起点,那么它就是儿子要满足的情况和不含染色起点的情况一样,只是转移花费变成了原花费;

二、第二种情况就是,他自身不是染色起点,在它的儿子中选取一个儿子,该儿子的子树中存在染色起点,为了保证最优,那么这中情况下该儿子的dp[v][颜色][1]与上述两种情况的差值最小( 因为差值最小才能保证,改变这棵子树为含有染色起点的子树,会对整体花费的增加造成最小的影响)、

代码:

#include <cstring>#include <algorithm>#include <cstdio>#include <iostream>#include <vector>#define MAX 107#define INF 0xfffffffusing namespace std;vector<int> e[MAX];int dp[MAX][2][2];//表示第i个点作为根节点的子树,改点涂为j色,是否从该点开始,记录当前的最小花费int cost[MAX][2];void dfs (  int u , int fa ){    if ( e[u].size() == 1 && fa != -1 )    {        for ( int i = 0 ; i < 2 ; i++ )            dp[u][i][0] = cost[u][i]/2 , dp[u][i][1] = cost[u][i];    //叶子节点只可能自身是起点或自身不是起点两种情况        return ;    }    for ( int i = 0 ; i < e[u].size( ) ; i++ )    {        int v = e[u][i];        if ( v == fa ) continue;        dfs ( v , u );    }    for ( int i = 0 ; i < 2 ; i++ )    {        int sum = 0 , dif = INF;        int cst = cost[u][i];        for ( int j = 0 ; j < e[u].size() ; j++ )        {            int v = e[u][j];            if ( v == fa ) continue;            int temp = min ( dp[v][i][0] , dp[v][i^1][1] );            //子树中存在起点的不同颜色和不存在节点的相同颜色中较小的            sum += temp;            dif = min ( dif , dp[v][i][1] - temp );            //将不同的颜色转换成相同颜色的差值        }        dp[u][i][0] = cst/2 + sum;        dp[u][i][1] = min ( cst + sum , cst/2 + sum + dif );        //分起点在根部和起点在子树里两种情况    }}int n;int main ( ){    int a ,b;    while ( ~scanf ( "%d" , &n ) )    {        for ( int i = 0 ; i <= n ; i++ ) e[i].clear ();        memset ( dp , 0x3f , sizeof ( dp ));        for ( int i = 0 ; i < 2 ; i++ )            for ( int j = 1 ; j <= n ; j++ )                scanf ( "%d" , &cost[j][i] );        for ( int i = 1 ; i < n ; i++ )        {            scanf ( "%d%d" , &a , &b );            e[a].push_back (b) , e[b].push_back (a);        }        dfs ( 1 , -1 );        //整棵树中一定存在起点,所以直接取两种颜色有起点的相对小的值        printf ( "%d\n" , min ( dp[1][0][1] , dp[1][1][1] ) );    }}


0 0