hdu 4003 Find Metal Mineral 遍历树 贪心

分析:

题意大家都懂,网上清一色的给出了树上的dp和分组背包的思想,但是仔细分析这题,是有贪心的特性的。

首先以S为根把树转化为有根树,如果K为1,那么K最后停留的节点到根的距离就只走了一遍,其他路径都是两遍,以此类推,如果K为2,并且两条路径没有公共的边,那么就是两条路径只走了一遍,因此我们只要求K条权值最大的路径,然后用树的总边权*2 减去它即可。我们不妨设总路径为为W,K条路径减少的代价为E;

如何处理有公共边的情况呢,如果一条边经过了i个机器人,那么,他对E贡献了 (2-i) * e 的权值。所以当我们选则了一条从未走过的路以后,我们不妨把这条路的每条边权值变成负的,请看对于一条边e,第一条路走过,贡献了 e = (2-1)*e, 然后e变成-e, 第二条贡献了-e,那么两次一共贡献了 e + (-e) = (2-2) *e,以此类推,34567。。。完美的解决了重复边的情况。

一共K次查找最大的路劲,每次要遍历一遍树,每次修改一条路径,故时间复杂度O(n*k)。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 10009;
int head[N];
struct Edge{
    int v, c, next;
}edge[N<<1];
int p[N],c[N],d[N];
bool vi[N];
int e;
void addedge(int u, int v, int cost){
    edge[e].v = v;
    edge[e].c = cost;
    edge[e].next = head[u];
    head[u] = e++;
}
void dfs(int u, int fa){ //转化为有根树
 //   printf("%d %d\n",u,c[u]);
    for(int i = head[u]; ~i; i = edge[i].next){
        int v = edge[i].v;
        if(v != fa){
            p[v] = u;
            c[v] = edge[i].c;
            dfs(v, u);
        }
    }
}
int s[N];
int top;
int find(int i){ //递归可能爆栈(没赶试),所以模拟递归的过程
    int t = i;
    while(true){
        if(!vi[i]&&i!=p[i]){
            s[top++] = i;
            i = p[i];
        }else{
            while(top){
                int j = s[--top];
                d[j] = d[p[j]] + c[j];
                vi[j] = true;
            }
            break;
        }
    }
    return d[t];
}
void inline update(int i){//把从节点到根的路线权值变为负的
    while(c[i]>0){
        c[i] = -c[i];
        i = p[i];
    }
}
int main(){
    int n,s,k,u,v,cost;
    while(scanf("%d%d%d", &n, &s, &k) == 3){
        memset(head, -1, sizeof(head));
        e = 0;
        int sum = 0;
        for(int i = 1; i < n; i++){
            scanf("%d%d%d",&u, &v, &cost);
            sum += cost;
            addedge(u,v,cost);
            addedge(v,u,cost);
        }
        p[s] = s;
        c[s] = d[s] = 0;
        dfs(s, -1);
        int sum2 = 0;
        for(int i = 0; i < k; i++){
            int ma = -0x7fffffff;
            int idex = -1;
            memset(vi, false, sizeof(vi));
         //   printf("%d : \n",i+1);
            for(int j = 1; j <= n; j++){
            //    printf("%d %d\n",j,find(j));
                if(j != s && find(j) > ma){
                  //  printf("%d: %d\n", j ,d[j]);
                    ma = d[j];
                    idex = j;
                }
            }
            if(ma > 0){
                sum2 += ma;
                update(idex);
            }else{
                break;
            }
        }
        printf("%d\n", sum * 2 - sum2);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值