【HDU 5758】Explorer Bo(树型dp)

【HDU 5758】Explorer Bo(树型dp)

Explorer Bo

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 489    Accepted Submission(s): 163

Problem Description
Explorer Bo likes exploring mazes around the world.Now he wants to explore a new maze.

The maze has N rooms connected with N1 roads of length 1 so that the maze looks like a tree.

Explorer Bo can transfer to a room immediately or walk along a road which is not the one he walked just now.

Because the transfer costs too much, Mr Bo will minimum the transfer using times firstly.

Mr Bo wants to walk along all the roads at least once,but he is lazy and he wants to minimum the total length he walked.

Please help him!

Initial point can be arbitrarily selected

Input
The first line of input contains only one integer T(=20), the number of test cases.

For each case, the first line contains 1 integers, N(100000) as described before. The following N1 lines describe the path. Each line has 2 integers, X,Y(1<=X,Y<=N),that there is a road between X and Y.

Output
Each output should occupy one line.For each case, just output the minimum length to explore all roads.

Sample Input
2
3
1 2
2 3
7
1 2
1 3
2 4
2 5
3 6
3 7

Sample Output
2
8

Source
2016 Multi-University Training Contest 3

题目大意:
一棵n个点的树。
可以由树上一个节点走到父亲或儿子。
但不可以走前一次走过的边。
可以飞行,即传送到任何一个点。

找到飞行最少次的条件下走的最少的边数并输出。

细想可知,要求飞行次数最少。
即飞(leaf+1)/2(包含第一次飞行,初始时刻在树外)

可以考虑所有叶子走到根的距离和。
要满足最短,即对于非根节点u,假设子树能遍历到c个儿子。
如果c是偶数,那么就需要至少两条路继续往u的父亲延伸。
如果c是奇数,一条路往u的父亲延伸即可。

因为偶数可以两两配对,但要求找到最短遍历路数,所以需要有叶子继续往上走,但没法只走一个,所以只能走一对。
如果是奇数的话,正好多出一个接着往上走就行了。

对于总叶子数为偶数的情况,如上遍历即可。

如果总叶子数为奇数,那么就需要找一个叶子f,只走到上方的一个岔路口即停止。也就是有一次飞行飞到这个叶子f,并往上走到岔路口即可。
或者说,这段路就是多出来的。剪去后,其实就是偶数的情况。

那么继续从岔路口往上考虑
如果有一条边u->v之前统计的是走过两次,那么把f走上来的那次减去即可。
如果u->v之前只统计过一次,要么这次是从f走上去的,要么是从别的叶子走的。
如果是f走过去的,那么直接减一的话这条路就会断,那么就需要跟之前一样拿出来一对给这条路,其实就是在原先1的基础上+1变成2。
如果不是从f走过去的,那么原先u的子树中的叶子肯定有一个跟f配对,剪去f所在支路后,没有跟那个叶子配对的,只能让它接着往上走,其实还是 1+1=2

最后在dp的过程中选择能减去的最多的路径数减去即可。

其次注意初始化根后继需要有至少2个孩子,否则处理出来要更繁琐。
换句话说 n <= 2直接输出n-1,否则就一直找到至少有两个分叉的点作为根,而且保证n >= 3的时候肯定会存在这种根。想不出来画画就知道了

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;

struct Edge
{
    int v,next,cnt;
};

Edge eg[2111111];
int head[111111];
int dp[111111];
int tp;

void Add(int u,int v)
{
    eg[tp].v = v;
    eg[tp].next = head[u];
    head[u] = tp++;
}

int ans;
//叶子数
int leaf;
int dfs(int u,int pre)
{
    int v;
    dp[u] = 0;
    int cnt = 0;
    for(int i = head[u]; i != -1; i = eg[i].next)
    {
        v = eg[i].v;
        if(v == pre) continue;
        //i这条路被走过的次数
        eg[i].cnt = dfs(v,u);
        cnt += eg[i].cnt;
        dp[u] += eg[i].cnt;
    }
    ans += cnt;
    //u是叶子
    if(cnt == 0) leaf++;
    //per->u这条路被奇数个点走过
    if(cnt == 0 || (cnt&1)) return 1;
    return 2;
}

int search(int u,int pre)
{
    int v;
    int mx = 0;
    for(int i = head[u]; i != -1; i = eg[i].next)
    {
        v = eg[i].v;
        if(v == pre) continue;
        //路被走两次 可消贡献+1 被走一次 负载+1
        mx = max(mx,search(v,u)+(eg[i].cnt==2? 1: -1));
    }
    return mx;
}

int main()
{
    //fread("1007.in");
    //fwrite("test.out");

    int t,n,u,v;

    scanf("%d",&t);

    while(t--)
    {
        scanf("%d",&n);

        memset(head,-1,sizeof(head));
        tp = 0;
        for(int i = 1; i < n; ++i)
        {
            scanf("%d%d",&u,&v);
            Add(u,v);
            Add(v,u);
        }

        if(n <= 2)
        {
            printf("%d\n",n-1);
            continue;
        }

        int root = 1;
        while(eg[head[root]].next == -1) root++;

        leaf = ans = 0;
        dfs(root,root);

        //printf("leaf:%d\n",leaf);
        if(leaf&1) ans -= search(root,root);

        printf("%d\n",ans);
    }

    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值