题目大意:
给一棵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)来说,首先可以确定的状态是:
为什么成立呢?是因为派出了一个机器人扫描完e这个子树,返回了,相当于没排除,对于u节点来说还是派出了k个,这样也方便处理下一个子树时,这个子树的回边问题(第一个样例)
并且这样保证了一定选择了每组物品中的一个
接下来,遍历这个组的其他物品,看能否对当前的dp[u][k]造成影响就好了
总之,这个题不是怎么好做的.
但是,确实是个好题!
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;
}