http://acm.hdu.edu.cn/showproblem.php?pid=6060 傳送門
題目大意:
有一棵樹T,n個點,n-1條邊;將2,3,4,5,6,… n分到k個集合裏面S1,S2,S3,… Sk;集合可以是空集,並且任意兩個集合之間沒有相同的點;定義一個函數f(S)=f({1}USi)爲集合S中的最小斯坦納樹,即爲每個集合中所有點加編號爲1的節點相互連接所經過的邊的權值之和,求k個點集權值最大的和;
如:對於Si={3,4},f(S)=f({1}U{3,4})的值爲點1點2再到點3的值,加上點1到點2再到4的值之和;
解題思路:首先要知道,題目中所給的樹就是一顆最小生成樹,然後求f(S)=f({1}USi);
用題目給的樣例來解釋一下:
k=4,要求把2-5分到4個集合裏面,當然有些集合可以是空集
1.沒有空集的時候,S1={2},S2={3},S3={4},S4={5};
f(S1)=f({1}U{2})=3; f(S2)=f({1}U{3})=7; f(S3)=f({1}U{4})=8; f(S4)=f({1}U{5})=9;
res=f(S1)+f(S2)+f(S3)+f(S4)=27;
2.有1個空集,{2,3},{4},{5},{空集},res=24,其餘兩種相同
3.有2個空集,{2,3,4},{5},{空集},{空集},res=21,還有兩種情況res值相同
4.有3個空集,{2,3,4,5},{空集},{空集},{空集},res=18;
由上可知,空集的存在只會使得res變小,題目要求求最大res,所以就應該把2-n分到的k個集合中,並且集合裏面沒有空集的存在
求最大的res,而res是由邊權加起來的,也就是說我們加上的邊越多,res越大,所以應該儘量使得每條邊都應該被走到;
如上圖:考慮邊A-C所做的貢獻,目的使得A-C邊被走的多一些,所以應該使得C點或者C點的子節點屬於集合中,那麼邊A-C就會被走過;並且應該儘量使得C點或者C點的子節點分佈於更多的集合中;設C點和C點的子節點數爲sum;
/*若是 sum<k,這個時候C點和C點的子節點最多能分到sum個集合裏面,A-C邊最多被走過sum次
若是 sum>k,這個時候C點和C點的子節點最多能分到k個集合裏面,A-C邊最多被走過k次*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=1e7+10;
typedef long long LL;
int n,k,tot;
int head[maxn],siz[maxn];
int w[maxn];
struct
{
int from,to,val,next;
} G[maxn*2];
void add_edge(int from,int to,int val)
{
G[tot].from=from;
G[tot].to=to;
G[tot].val=val;
G[tot].next=head[from];
head[from]=tot;
tot++;
}
void dfs(int x,int father_x)
{
siz[x]=1;
for(int i=head[x]; i!=-1; i=G[i].next)
{
int to=G[i].to;
if(to==father_x) continue;
w[to]=G[i].val;
dfs(to,x);
siz[x]+=siz[to];
}
}
int main()
{
int a,b,c;
while(~scanf("%d%d",&n,&k))
{
memset(head,-1,sizeof(head));
memset(siz,0,sizeof(siz));
memset(w,0,sizeof(w));
tot=0;
for(int i=1; i<=n-1; i++) ///n-1條邊
{
scanf("%d%d%d",&a,&b,&c);
add_edge(a,b,c);
}
dfs(1,-1);
LL ans=0;
for(int i=1; i<=n; i++)
ans+=(LL)w[i]*min(k,siz[i]);
printf("%lld\n",ans);
}
}