hdu4679 Terrorist’s destroy 解题报告

1 篇文章 0 订阅

题意:给你一棵树和边上的权值,定义去掉一条边的花费为边权值(a)乘上b,b定义为去掉边后形成的两棵树中两点间的最远距离(注意是各自内部的最远距离),问去掉哪条边的花费最少?

分析:首先我们可以将树的直径找出来,如下图


假设1->6就是这棵树的直径,非直径上的边为枝条,有两种删除边的方式:

1.去掉的是枝条

2.去掉的是直径上的边

方式1:b值一定是直径长度,可以枚举直接处理。

方式2:我们先把直径上的点做预处理,dep[i]表示第i个点的最深的枝条深度(如dep[1] = 1; dep[2] = 2; dep[3] = 3; dep[4] = 1; dep[5] = 2;dep[6] = 1)

f1[i] 表示删除第i个点右边的边左侧能得到的b值   f2[i] 表示删除第i个点左边的边右侧能得到的b值

(如f1[1] = 0; f1[2] = 2; f1[3] = 4;  f1[4] =4; f1[5] =5; ) 那么f1 ,f2的值怎么求呢? 可以证明f1的值只能是(一部分直径+一条枝条的长度)或是一部分直径的长度,绝不可能是由两段枝条组成。

所以我们有递推式 f1[i] = max{f1[i-1],i-1+dep[i]-1}       注意:这里的i是指从左边数第几个点,并非编号

f2递推式类似,有了f1和f2数组我们就可以枚举直径上的边了,从而在O(1)的时间算得每次的花费

c++代码

#include<cstdio>
#include<cstring>
const int INF = 2000000000;
const int MAXX = 200020;
int q[MAXX],dislong[MAXX],f1[MAXX],f2[MAXX],pre[MAXX],father[MAXX],dep[MAXX];
int list[MAXX],next[MAXX],p[MAXX],c[MAXX],order[MAXX],maxx,n;
bool b[MAXX],inlong[MAXX];

int getmax(int x,int y)
{
    return x>y?x:y;
}
int getmin(int x,int y)
{
    return x>y?y:x;
}
int findlong(int xx,int n)
{
    int t,w,now,k,x;
    for (int i = 1; i <= n; i++)
    {
        b[i] = true;
    }
    t = 0; w = 1;
    q[1] = xx;
    dislong[1] = 1;
    b[xx] = false;
    pre[xx] = 0;
    maxx = 0;
    while (t < w)
    {
        t++; x = q[t];
        k = list[x];
        while (k > 0)
        {
            if (b[p[k]] == true)
            {
                w++;
                b[p[k]] = false;
                q[w] = p[k];
                dislong[w] = dislong[t]+1;
                pre[p[k]] = x;
                if (dislong[w] > maxx) {maxx = dislong[w]; now = p[k];}
            }
            k = next[k];
        }
    }
    return now;
}
void init(int n)
{
    for (int i = 1; i <= n; i++)
    {
        list[i] = 0;
    }
}
void dfs(int x,int pre1)
{
    int k;
    dep[x] = 1;
    k = list[x];
    while (k > 0)
    {
        if (inlong[p[k]] == false && p[k] != pre1)
        {
            dfs(p[k],x);
            dep[x] = getmax(dep[x],dep[p[k]]+1);
        }
        k = next[k];
    }
}
void finddep()
{
    int i;
    for (i = 1; i <= n; i++)
    if (inlong[i])
    {

        dfs(i,0);
    }
}
int main()
{
    int step,temp,kase,tot,i,k,kk,x,y,z,front,rear;
    int sum,ans,result;
    scanf("%d",&temp);
    for (kase = 1; kase <= temp; kase++)
    {
        tot = 0;
        scanf("%d",&n);
        init(n);
        for (i = 1;i < n; i++)
        {
            scanf("%d%d%d",&x,&y,&z);
            tot++;
            next[tot] = list[x];
            list[x] = tot;
            p[tot] = y;
            c[tot] = z;
            order[tot] = i;
            tot++;
            next[tot] = list[y];
            list[y] = tot;
            p[tot] = x;
            c[tot] = z;
            order[tot] = i;
        }
        if (n != 1)
        {
            memset(pre,0,sizeof(pre));
            front = findlong(1,n);
            rear = findlong(front,n);
            sum = maxx-1;

            k = rear;
            memset(inlong,false,sizeof(inlong));
            memset(father,0,sizeof(father));
            while (k > 0)
            {
                inlong[k] = true;
                father[pre[k]] = k;
                k = pre[k];
            }
            memset(dep,0,sizeof(dep));
            finddep();//
            //cong front -> rear
            memset(f1,0,sizeof(f1));
            memset(f2,0,sizeof(f2));
            k = front; step = 0;
            while (k != rear)
            {
                step++;
                f1[k] = getmax(f1[pre[k]],step-1+dep[k]-1); //?
                k = father[k];
            }
            // cong rear->front
            k = rear; step = 0;
            father[rear] = 0;
            while (k != front)
            {
                step++;
                f2[k] = getmax(f2[father[k]],step-1+dep[k]-1);
                k = pre[k];
            }
            //遍历直径
            ans = INF;
            result = INF;
            k = front;
            while (k != rear)
            {
                kk = list[k];
                while (kk > 0)
                {
                    if (p[kk] == father[k]) break;
                    kk = next[kk];
                }
                if (ans > c[kk]*getmax(f1[k],f2[father[k]]))
                {
                    ans = c[kk]*getmax(f1[k],f2[father[k]]);
                    result = order[kk];

                }
                else if (ans == c[kk]*getmax(f1[k],f2[father[k]]))
                {
                    if (result > order[kk]) result = order[kk];
                }
                k = father[k];
            }

            //遍历枝条
            for (i = 1; i <= n; i++)
            {
                k = list[i];
                while (k > 0)
                {
                    if(!(inlong[i] && inlong[p[k]]))
                    {
                        if (ans > c[k]*sum)
                        {
                            ans = c[k]*sum;
                            result = order[k];

                        }
                        else if (ans == c[k]*sum)
                        {
                            if (result > order[k]) result = order[k];
                        }
                    }
                    k = next[k];
                }
            }
            printf("Case #%d: ",kase);
            printf("%d\n",result);
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值