分析:
题意大家都懂,网上清一色的给出了树上的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;
}