HDU6060 RXD and dividing
求树的子节点个数
描述
RXD has a tree T, with the size of n. Each edge has a cost.
Define f(S) as the the cost of the minimal Steiner Tree of the set S on tree T.
he wants to divide 2,3,4,5,6,…n into k parts S1,S2,S3,…Sk,
where ⋃Si={2,3,…,n} and for all different i,j , we can conclude that Si⋂Sj=∅.
Then he calulates res=∑ki=1f({1}⋃Si).
He wants to maximize the res.
1≤k≤n≤106
the cost of each edge∈[1,105]
Si might be empty.
f(S) means that you need to choose a couple of edges on the tree to make all the points in S connected, and you need to minimize the sum of the cost of these edges. f(S) is equal to the minimal cost
输入
There are several test cases, please keep reading until EOF.
For each test case, the first line consists of 2 integer n,k, which means the number of the tree nodes , and k means the number of parts.
The next n−1 lines consists of 2 integers, a,b,c, means a tree edge (a,b) with cost c.
It is guaranteed that the edges would form a tree.
There are 4 big test cases and 50 small test cases.
small test case means n≤100.
输出
For each test case, output an integer, which means the answer.
样例输入
5 4
1 2 3
2 3 4
2 4 5
2 5 6
样例输出
27
题意
给一棵树T,有n个结点。
给一个k,表示有k个集合,我们需要把2,3,4,…n号节点放入集合,要保证k个集合的并集等于{2,3,4,5…n},并且集合互不相交。(集合可以为空)
然后每次取一个集合Si与{1}求并,得到比如{1,2,3},那么tempi = f({1,2,3});f({1}并Si)的意思是把集合内的所有点连接起来的边的权值和。最后把所有权值和相加的到答案。
最后问你能够得到最大的答案。
思路:
我们要想得到最大的答案,那么就要尽可能的去利用这些边,也就是尽可能重复计算这些边。
那么我们想,假设先从叶子节点开始,把这些叶子节点放入一个集合,那么这个集合的temp值就会把所有的边都算一遍。那么下次我们取所有叶子节点的父亲,放入一个集合,那么这个集合的temp值会把除了叶子节点到父亲的那条那边的其他所有边都算一遍。因为集合可以为空,以此类推,我们就可以得到最大的答案。但是如果遇到集合不够的情况,就把剩下的所有点加入最后一个集合。
那么有以上分析,其实就是算每条边会算多少次,比如叶子节点到父亲的那条边会算一次。其实一条边会算多少次跟某个点的所有子孙节点个数有关,就比如样例中,2号点有3个子孙节点, 那么2号点连接父节点的那条边会算3+1次。3号点有0个子孙节点,那么3号点连接父节点的那条边会算0+1次。
那么其实问题就是转化为求每个点的子孙节点个数,然后算出每条边要重复计算的次数即可。
AC代码
//要加双向边因为题目中只是说了 某两个点中有一条边但是没说谁是谁的父亲
//设双向边然后之后通过一系列判断看到底谁是谁的父亲
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 1e6+10;
int n,k;
int ans;
bool visit[maxn]; //因为是双向边所以要有visit数组,防止重复访问
int cnt[maxn]; //记录某个点到父节点的那个边该算多少次
int depth[maxn]; //记录深度,防止重复计算
struct Edge{
int link;
long long cost;
};
vector< vector<struct Edge> > map(maxn);
void dfs(int i,int dep)
{
//边界条件
//容量等于1并且不是根节点,那么就一定叶子节点(有1容量是因为双向边)
if(map[i].size() == 1 && i != 1)
cnt[i] = 1;
depth[i] = dep;
visit[i] = true;
for(int j = 0; j < map[i].size() ;j ++ )
{
//未访问过
if(visit[map[i][j].link] == false)
{
//递归访问子节点,深度加1
dfs(map[i][j].link, dep + 1);
//子节点的cnt是已经算出的
cnt[i] += cnt[map[i][j].link];
}
}
}
void init()
{
for(int i = 1; i <= n ;i ++)
map[i].clear();
memset(visit,false,sizeof(visit));
for(int i = 1; i <= n ;i ++)
cnt[i] = 1;
memset(depth,0,sizeof(depth));
}
int main()
{
while(scanf("%d%d",&n,&k) != EOF)
{
init();
for(int i = 0; i < n-1 ;i ++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
Edge edge1; edge1.link = b; edge1.cost = c;
Edge edge2; edge2.link = a; edge2.cost = c;
//要加双向边因为题目中只是说了 某两个点中有一条边但是没说谁是谁的父亲
//设双向边然后之后通过一系列判断看到底谁是谁的父亲
map[a].push_back(edge1);
map[b].push_back(edge2);
}
//看每个点有多少子孙节点,同时记录深度
//是为了之后算总cost的时候防止因为双向边而重复计算
dfs(1,0);
long long ans = 0;
for(int i = 2; i <= n ; i ++)
{
for(int j = 0; j < map[i].size() ; j ++)
{
//访问那条边的link的深度必须要小于i ,才能算 防止重复计算
//相当于每次算一个点的链接父亲节点的那条边
if(depth[map[i][j].link] < depth[i] )
//如果次数大于k,那么就算k次
ans += min(k, cnt[i]) * map[i][j].cost;
}
}
printf("%lld\n",ans);
}
return 0;
}