【HDU4313】 - Matrix - 树状DP Version 思路+解题报告+AC代码【0.4%达成】

7 篇文章 0 订阅
5 篇文章 0 订阅
#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;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值