UVa 12161 Ironman Race in Treeland(树分治)


题目大意:

    有一棵树,每条边上都有花费和长度,求花费不超过M的最长路径。


解题思路:

    比较典型的树分治,对于每个重心,统计所有经过重心的路径的组合,再加上原始输入的边,一定能够得到所有的路径。在计算组合的时候,直接暴力枚举非常低效,我们可以先去掉花费大,长度短的路径,然后排序利用双指针(具体写法见代码)。


AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <string>
#include <map>
#include <set>
#include <list>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXV=30000+3;

struct Edge
{
    int to, damage, length, next;
    Edge(int t=0, int d=0, int l=0, int n=0):to(t), damage(d), length(l), next(n){}
}edge[MAXV*2];

int N, M;
int head[MAXV], edge_size;
int ans, pre_max_subtree_size;//重心最大子树大小
int pre_subtree_size;//以u为根的子树大小
int subtree_size[MAXV];//以i为根的子树的节点数
bool is_centroid[MAXV];//是否为重心
pair<int, int> path[MAXV];
int b, e;//当前子树中路径在path[]中的开始下标和结束下标

void init()
{
    ans=0;
    edge_size=0;
    mem(head, -1);
}

void add_edge(int from, int to, int damage, int length)
{
    edge[edge_size]=Edge(to, damage, length, head[from]);
    head[from]=edge_size++;
}

//查找重心的递归函数
//在以u为根的子树中寻找一个顶点,使得删除该该节点后最大子树的顶点数最小
void search_centroid(int u, int fa, int &root)
{
    int max_subtree_size=0;
    subtree_size[u]=1;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa || is_centroid[v])
            continue;
        search_centroid(v, u, root);
        max_subtree_size=max(max_subtree_size, subtree_size[v]);
        subtree_size[u]+=subtree_size[v];
    }
    max_subtree_size=max(max_subtree_size, pre_subtree_size-subtree_size[u]);//计算u所在的子树的结点数
    if(max_subtree_size<pre_max_subtree_size)//更换重心
    {
        root=u;
        pre_max_subtree_size=max_subtree_size;
    }
}

//计算子树大小(subtree_size)的递归函数
int compute_subtree_size(int u, int fa)
{
    int res=1;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa || is_centroid[v])
            continue;
        res+=compute_subtree_size(v, u);
    }
    return res;
}

//计算子树中的所有顶点到重心的距离
void enumerate_paths(int u, int fa, int damage, int length)
{
    path[e++]=make_pair(damage, length);
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa || is_centroid[v])
            continue;
        if(damage+edge[i].damage<=M)
            enumerate_paths(v, u, damage+edge[i].damage, length+edge[i].length);
    }
}

//删除长度小,花费大的路径
void remove_useless(int b, int &e)
{
    if(b==e)
        return ;
    int size;
    for(int i=size=b+1;i<e;++i)
    {
        if(path[i].fi==path[size-1].fi)
            continue;
        if(path[i].se<=path[size-1].se)
            continue;
        path[size++]=path[i];
    }
    e=size;
}

void solve_sub_problem(int u, int fa)
{
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa || is_centroid[v])
            continue;
        pre_max_subtree_size=MAXV;
        pre_subtree_size=compute_subtree_size(v, u);
        int root;
        search_centroid(v, u, root);
        is_centroid[root]=1;
        solve_sub_problem(root, u);
        is_centroid[root]=0;
    }
    b=e=0;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa || is_centroid[v])
            continue;
        if(edge[i].damage<=M)
            enumerate_paths(v, u, edge[i].damage, edge[i].length);
        if(b>0)//合并不同子树之间经过重心的路径
        {
            sort(path+b, path+e);
            remove_useless(b, e);
            for(int _b=0, _e=e-1;_b<b;++_b)
            {
                while(_e>=b && path[_b].fi+path[_e].fi>M)
                    --_e;
                if(_e>=b)
                    ans=max(ans, path[_b].se+path[_e].se);
            }
        }
        sort(path, path+e);
        remove_useless(0, e);
        b=e;
    }
}

int main()
{
    int T_T;
    scanf("%d", &T_T);
    for(int cas=1;cas<=T_T;++cas)
    {
        scanf("%d%d", &N, &M);
        init();
        for(int i=1;i<N;++i)
        {
            int u, v, damage, length;
            scanf("%d%d%d%d", &u, &v, &damage, &length);
            --u;
            --v;
            add_edge(u, v, damage, length);
            add_edge(v, u, damage, length);
            if(damage<=M)
                ans=max(ans, length);
        }
        int root;
        pre_max_subtree_size=MAXV;
        pre_subtree_size=N;
        search_centroid(1, -1, root);
        is_centroid[root]=1;
        solve_sub_problem(root, -1);
        is_centroid[root]=0;
        printf("Case %d: %d\n", cas, ans);
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值