題意
給出一顆n個節點的樹,要求將2-n號節點分成k個集合,然後對於每個集合加上1號節點算一個聯通的最小花費(最小斯坦納樹),定義爲每一部分的value,問總的value最大可能是多少。
題解
因爲給出的n個節點本身滿足是一個樹,所以對於每個集合求的最小斯坦納樹其實就是最小生成樹就是在給出樹中聯通集合所有節點加上1號節點的所有邊權和。
因爲每個集合都包含1這個點,因此對於每個點都至少有一條到1的路徑。可以從1開始DFS,對於每個點u,它和父親的邊的貢獻最多可以是min(son[u], k)(son[u]表示包含u的所有子節點的個數)。因爲可以把u的兒子結點分在不同的k個集合裏面,這些兒子結點都必須經過u和父親的邊才能到達1。所以這條邊對於結果的最大貢獻就是邊權*min(son[u],k)。然後通過dfs得出所有son[]和edge_wight[]。
具體可看代碼註釋。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
int n,k;
//son[i]保存i節點的所有子節點的個數(包括i節點)
//edge_wight[i]保存i節點和其父節點的邊權
int son[maxn],edge_wight[maxn];
struct Edge{
int to,len;
Edge(int a,int b)
{
to = a;
len = b;
}
};
vector<Edge> edge[maxn];
void dfs(int root,int fa)
{
son[root]=1;
//遍歷與root相連的點
for(int i=0;i<edge[root].size();i++)
{
int next = edge[root][i].to;
if(next==fa) continue;
edge_wight[next] = edge[root][i].len;
dfs(next,root);
son[root] += son[next];
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(~scanf("%d%d",&n,&k))
{
int u,v,len;
for(int i=1;i<=n;i++) edge[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&len);
//加邊
edge[u].push_back(Edge(v,len));
edge[v].push_back(Edge(u,len));
}
dfs(1,0);
ll ans=0;
//這裏因爲edge_wight[]開的是全局,自動賦0值,所以edge_wight[1]就是0,其它值通過dfs得出,不用初始化
for(int i=1;i<=n;i++)
ans += 1ll*edge_wight[i]*min(son[i],k);
printf("%lld\n",ans);
}
//printf("Time used = %.2f\n",(double)clock() / CLOCKS_PER_SEC);
return 0;
}