d森林问题
1. 问题描述
设T为一带权树,树中的每个边的权都为整数。又设S为T的一个顶点的子集,从T中删除S中的所有结点,则得到一个森林,记为T/S。如果T/S中所有树从根到叶子节点的路径长度都不超过d,则称T/S是一个d森林。设计一个算法求T的最小顶点集合S,使T/S为一个d森林。
2. 题目分析
本问题可用贪心算法解。从每个叶子节点向根节点移动,若路途中路长超过d则将子树分离。(1)贪心选择性质:若某一最优解某一步不符合贪心选择,则该步骤得到的子树长度要么和贪心选择得到的相同,要么比贪心选择得到的要短。若相同则贪心选择也可以得到最优解;否则原解不是最优解。(2)最优子结构性质:原树的某一子树删去S中结点(若有)显然也满足森林从根到叶子节点的路径长度都不超过d。
3. 程序设计
数组leaf存储叶节点;数组isCut存储某一结点是否被删去;数组parent存储双亲结点;数组parlen存储某节点到双亲结点的权值;数组dis存储某结点到叶节点的距离.函数cutAndDelete用于问题求解.若当前节点不是根节点,则向上移动,路长超过d且未分离时将结点删除并向根节点移动;若没有超过d则更新dist数组.若出度为0则将该结点添加到叶子节点数组leaf.主函数中先输入并建立树,再调用cutAndDelete求解.
4. 代码
#include "iostream"
#include "fstream"
#include "queue"
using namespace std;
const int maximum = 10000;
int leaf[maximum];
int isCut[maximum];
int parent[maximum];
int parlen[maximum];
int dis[maximum];
int outdeg[maximum];
int cutAndDelete(int n, int d)
{
int i = 0; int num = 0;
for (i = 1; i <= leaf[0]; i++)
{
if (leaf[i] != 1)
{
int p = parent[leaf[i]];
int len = parlen[leaf[i]];
if (isCut[p] == 0 && dis[leaf[i]] + len > d)
{
num++;
isCut[p] = 1;
p = parent[p];
}
else if (isCut[p] == 0 && dis[p] < len + dis[leaf[i]])
{
dis[p] = len + dis[leaf[i]];
}
if (--outdeg[p] == 0)
{
leaf[++leaf[0]] = p;
}
}
}
return num;
}
int main()
{
ifstream input("exp3_in.txt");
int nodenum, a, i, j, k, n, d, t;
input >> nodenum;
for (i = 0; i < nodenum; i++)
{
memset(leaf, 0, sizeof(leaf));
memset(parlen, 0, sizeof(parlen));
memset(isCut, 0, sizeof(isCut));
memset(dis, 0, sizeof(dis));
memset(outdeg, 0, sizeof(outdeg));
memset(parent, 0, sizeof(parent));
input >> n >> d;
for (j = 1; j <= n; j++)
{
input >> outdeg[j];
if (outdeg[j] != 0)
{
for (k = 0; k < outdeg[j]; k++)
{
input >> t;
t += 1;
input >> a;
parent[t] = j;
parlen[t] = a;
}
}
else if (outdeg[j] == 0)
{
leaf[++leaf[0]] = j;
}
}
int result;
result = cutAndDelete(n, d);
cout << result << endl;
}
return 0;
}
5. 运行结果