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;
}

 

 

posted @ 2013-04-07 13:31  Titanium  阅读(763)  评论(0编辑  收藏  举报