【HDU4003】 Find Metal Mineral | 树形dp、思维

题目大意:

给一棵n个节点的树, 节点编号为1~n, 每条边都有一个花费值.
有k个机器人从S点出发, 问让机器人遍历所有边,最少花费值多少?

题目思路:

卡了两天的树形dp,一开始根本搞不懂如何处理回边。

那就简单分析一下了:

令dp[i][k]代表以i为根的子树,派出去k个遍历完子树

这里为了方便处理回边,所以状态的定义是: 派出去k个遍历完子树不返回,那么k = 0自然就是有回边的情况了

现在考虑一下回边的状态:

对于一棵子树,全部遍历完然后再返回根节点,权值是 子树的边权值和 * 2,那么当这颗子树遍历完,向父亲节点返回时,返回一个机器人还是多个呢?显然是一个,因为遍历完子树回来的权值是相同的,那么不可能派出多个然后在返回,因为会多出几条根节点到该节点 派出机器人的路径

回边问题解决以后,那么这个问题就解决一大部分了

将子树的k = 0,1,2,3,4,5,p,看作每个组的p种物品,这样就是说,对于一个节点u,现在面临m个组(m是孩子个数),每组都有p个物品,对于每一组来说,必须要选择一个物品,问最小的价值?

显然成为一个背包问题了,这时候k = 0的重要性就体现出来了:

对于边e(u,e,w)来说,首先可以确定的状态是:

dp[u][k] += dp[e][0]*2*w

为什么成立呢?是因为派出了一个机器人扫描完e这个子树,返回了,相当于没排除,对于u节点来说还是派出了k个,这样也方便处理下一个子树时,这个子树的回边问题(第一个样例)

并且这样保证了一定选择了每组物品中的一个

接下来,遍历这个组的其他物品,看能否对当前的dp[u][k]造成影响就好了

for(int\ j=1;j<=k;j++)

\ \ \ \ \ \ \ \ \ \ \ \ \ dp[u][k] = min(dp[u][k],dp[u][k-j]+j*x.second+dp[e][j]);

总之,这个题不是怎么好做的.

但是,确实是个好题!

Code:

/*** keep hungry and calm CoolGuang!  ***/
#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
#define d(x) printf("%lld\n",x);
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const ll INF= 1e17;
const ll maxn = 2e5+700;
const int mod= 1e9+7;
const int up = 1e9;
template<typename T>inline void read(T &a){char c=getchar();T x=0,f=1;while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}a=f*x;}
ll n,m,p;
vector<pair<int,int>>v[maxn];
ll dp[maxn][12];
void dfs(int u,int fa){
    for(auto x:v[u]){
        int e = x.first;
        if(e == fa) continue;
        dfs(e,u);
        for(int k=p;k>=1;k--){///容量
            dp[u][k] += dp[e][0] + 2*x.second;///分配一个回来,保证选
            for(int j=1;j<=k;j++)
                dp[u][k] = min(dp[u][k],dp[u][k-j]+j*x.second+dp[e][j]);
        }
        dp[u][0] += dp[e][0] + 2*x.second;
    }
}
int main(){
    while(~scanf("%lld%lld%lld",&n,&m,&p)){
        for(int i=1;i<=n;i++) v[i].clear();
        for(int i=1;i<=n-1;i++){
            int x,y,w;read(x);read(y);read(w);
            v[x].push_back({y,w});
            v[y].push_back({x,w});
        }
        for(int i=1;i<=n;i++)
            for(int k=0;k<=p;k++)
                dp[i][k] = 0;
        dfs(m,m);
        printf("%lld\n",dp[m][p]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/111313348