• uva 10308 Roads in the North


    树型DP

    这题刘汝佳居然归在数学题里面,他的用意应该是想归在递推的,但是这题更应该属于一个经典树DP

    题意:给一个图,两个点间不会有重边,边时双向连通的,另外注意这句话,

    there is only one route from a village to a village that does not pass through some other village twice.

    这句话说明了,这个图是很特殊的,其实是一个无根树。要求的是,找出两点,他们的距离最远

    有两种思路,但是本质还是一样的,写法不同,推荐后面那种

    第一种:

    /*
    思考方法:这个图的本质是个无根树,所以我们指点任意一个节点为树根即可,因而指定1为树根,变为一棵树
    题目变为,在这颗树中找出两个点,它们的距离最远,可知这这两个点一定都是叶子节点,因为只要一个点不是叶子节点
    那么就可以继续往下或者往上延伸,距离还可以变长
    那么两个叶子节点,它们的距离是整个树里面最长的,它们一定是经过某个父节点的,这个父节点,一定是整个树的树根1吗
    显然不是,随便举一个例子就能证明
    那么我们假设a,b两点经过的父节点为rt。其实我们很容易知道,这个rt一定是LCA(最近公共祖先)
    但我们这里并不是求LCA。我们换个角度看这个问题,我们已经知道了a--->rt------>b,并且是最远距离
    我们可以描述为rt----->a + rt------>b = 最远距离 , 即一个根到它两个叶子的距离之和
    我们知道rt所在的子树可能有多个叶子,为什么选了a,b,肯定是因为rt到这两个叶子的距离最远
    所以对于一个根,在它的子树内,找出它到所有叶子的距离,并选出最大值和次大值。
    所以我们开辟两个数组d,dd,d[rt]表示rt到其子树叶子的最大值,dd[rt]表示rt到其子树叶子的次大值
    另外开辟一个数组dp[rt],表示rt为根的子树内,两点间的最远距离
    我们要求的实际上是dp[rt],dp[rt]有两种可能,
    dp[rt]=d[rt]+dd[rt],即这两个点是经过rt的
    dp[rt]=dp[son],即两个叶子节点没有经过rt,只经过了其子树的某个节点
    而对于d和dd怎么求呢?
    d[rt]=max{ d[son]+w } , 因为d是根到叶子的距离,所以必经过某个儿子。当可以更新d的时候,先把d给dd,
    那么就记录了次大值
    最后剩下建树,在这里只需要隐式建树即可,因为我们使用这棵树只要是为了遍历,遍历的话知道边的连接关系即可
    可以手工模拟邻接表建树,也可以直接用vector保存
    */

    注意输入:UVA的输入一贯蛋疼,case的分割就是一个空行,所以要处理掉这个空行。网上有人说,可能有坑,就是一个case里面什么都没有,用一个空行表示,然后再一个空行结束这个case,经过测试,这个坑是没有的,不必另外处理,但是我的代码里面已经做了这样的处理,都无关紧要,如果WA了,又确定自己算法没问题的话,不妨先检查一下自己的输入,可能就AC了,我就是个例子呵呵

    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <utility>
    using namespace std;
    #define N 10100
    #define INF 0x3f3f3f3f
    
    typedef pair<int,int> pii;
    vector<pii>a[N];
    int n;
    int d[N],dd[N],dp[N];
    bool vis[N];
    
    void add(int u ,int v ,int w)
    {
        pii tmp;
        tmp.first=v; tmp.second=w;
        a[u].push_back(tmp);
        tmp.first=u; tmp.second=w;
        a[v].push_back(tmp);
    }
    
    int max(int x, int y ,int z)
    {
        int ans;
        ans=x>y?x:y;
        ans=ans>z?ans:z;
        return ans;
    }
    
    void dfs(int rt)
    {
        vis[rt]=true;
        pii tmp;
        int size=a[rt].size();
        d[rt]=dd[rt]=dp[rt]=0;
        for(int i=0; i<size; i++)
        {
            int v,w;
            tmp=a[rt][i];
            v=tmp.first;
            w=tmp.second;
            if(!vis[v])
            {
                dfs(v);
                if(d[v]+w >= d[rt])
                {
                    dd[rt]=d[rt];
                    d[rt]=d[v]+w;
                }
                if(d[v]+w < d[rt] && d[v]+w > dd[rt])
                    dd[rt]=d[v]+w;
                dp[rt]=max(dp[rt] , d[rt]+dd[rt] , dp[v]);
            }
        }
        return ;
    }
    
    void solve()
    {
        memset(vis,false,sizeof(vis));
        dfs(1);
        printf("%d\n",dp[1]);
    }
    
    int main()
    {
        int u,v,w;  char str[50];  bool flag,End=false;
        while(!End)
        {
            for(int i=0; i<=N-100; i++) a[i].clear();
            flag=false;
            while(1)
            {
                if(!gets(str))
                { End=true; break;}
                if(!flag && str[0]=='\0') //空case,不过数据中是没有这个坑的,可以不用处理
                {
                    if(!gets(str)) End=true;
                    break;
                } 
                if(flag && str[0]=='\0') break; //一组数据的结束
                flag=true;
                sscanf(str,"%d%d%d",&u,&v,&w);
                add(u,v,w);
            }
            solve();
        }
        return 0;
    }

    第二种:基于上面的讲解,我们不难发现,这样做是有累赘的,其实关系很简单,无非就是说

    1.两个点距离最远,那么它们一定是叶子节点

    2.既然它们都是叶子,那么它们一定有一个最近公共祖先(特殊除外,即图中只有两个点,它们即使叶子也可以是祖先,但是丝毫不影响解题)

    3.这段距离可以描述成   rt--->a + rt----->b  ,并且可知这两个值一定是最大值和次大值

    4.既然这样,为什么我们不可以对每个根,都找出属于它们的rt-->a和rt--->b,然后两者相加,就可以得到以该点为中转站,得到的最远路径,然后再枚举所有rt,不就是得到了整个树中的最大值了吗?

    下面的代码就是这样做的,存边部分完全一样,只是修改了solve()和dfs()。dp[rt][1]表示以rt为根到叶子的最大值,dp[rt][0]表示以rt为根到叶子的次大值

    最终的答案就是  ans=max{ dp[rt][1]+dp[rt][0] }

    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <utility>
    using namespace std;
    #define N 10100
    #define INF 0x3f3f3f3f
    
    typedef pair<int,int> pii;
    vector<pii>a[N];
    int n,ans;
    int dp[N][2];
    bool vis[N];
    
    void add(int u ,int v ,int w)
    {
        pii tmp;
        tmp.first=v; tmp.second=w;
        a[u].push_back(tmp);
        tmp.first=u; tmp.second=w;
        a[v].push_back(tmp);
    }
    
    void dfs(int rt)
    {
        vis[rt]=true;
        
        pii tmp;
        int v,w,size=a[rt].size();
        
        dp[rt][1]=dp[rt][0]=0;
        for(int i=0; i<size; i++)
        {
            pii tmp=a[rt][i];
            v=tmp.first;
            w=tmp.second;
            if(!vis[v])
            {
                dfs(v);
                if(dp[v][1]+w > dp[rt][1])
                { 
                    dp[rt][0] = dp[rt][1];
                    dp[rt][1] = dp[v][1]+w;
                }
                else if(dp[v][1]+w > dp[rt][0])
                    dp[rt][0] = dp[v][1]+w;
            }
        }
        if(dp[rt][1]+dp[rt][0] > ans) ans = dp[rt][1]+dp[rt][0];
    }
    
    void solve()
    {
        memset(vis,false,sizeof(vis));
        ans=0;
        dfs(1);
        printf("%d\n",ans);
    }
    
    int main()
    {
        int u,v,w;  char str[50];  bool flag,End=false;
        while(!End)
        {
            for(int i=0; i<=N-100; i++) a[i].clear();
            flag=false;
            while(1)
            {
                if(!gets(str))
                { End=true; break;}
                if(!flag && str[0]=='\0') //空case,不过数据中是没有这个坑的,可以不用处理
                {
                    if(!gets(str)) End=true;
                    break;
                } 
                if(flag && str[0]=='\0') break; //一组数据的结束
                flag=true;
                sscanf(str,"%d%d%d",&u,&v,&w);
                add(u,v,w);
            }
            solve();
        }
        return 0;
    }
  • 相关阅读:
    Alpha 冲刺报告(5/10)
    Alpha 冲刺报告(4/10)
    Alpha 冲刺报告(3/10)
    Alpha 冲刺报告(2/10)
    Alpha 冲刺报告(1/10)
    项目需求分析答辩总结
    项目选题报告答辩总结
    项目UML设计(团队)
    第七次作业--项目需求分析(团队)
    第六次作业--结对编程第二次
  • 原文地址:https://www.cnblogs.com/scau20110726/p/3003926.html
Copyright © 2020-2023  润新知