实验3-d森林问题
1.问题描述
设 T T T 为一带权树,树中的每个边的权都为整数。又设 S S S为 T T T的一个顶点的子集,从 T T T中删除 S S S中的所有结点,则得到一个森林,记为 T / S T/S T/S。如果 T / S T/S T/S 中所有树从根到叶子节点的路径长度都不超过 d d d,则称 T / S T/S T/S是一个 d d d森林。
设计一个算法求 T T T的最小顶点集合 S S S,使 T / S T/S T/S为一 个 d d d森林。
输入格式
多组输入。
第一行输入树包含的顶点个数 n n n 和路径长度 d d d。其中,树包含的顶点我们将其从 0 0 0到 n − 1 n-1 n−1进 行编号, 0 0 0为根节点。接下来的 n n n行分别对应树的第 0 个顶点到 ( n − 1 ) (n-1) (n−1)个顶点的孩子节点的信息。具体地,每一行第一个元素 k k k 表示一共包含 k k k个孩子节点,接下来包含 2 k 2k 2k个元素,两 两配对分别表示孩子的编号 i d id id和边的权重 w w w。
除过第一行之外,后面的第 i i i行表示第 i − 1 i-1 i−1个节点的孩子节点数 k k k和对应的孩子节点编号和权重。
输出格式: 每组输出一行,表示删除的节点个数
数据规模: 2 < n ≤ 1 0 4 2 < n ≤ 10^4 2<n≤104
2.问题分析与算法设计
(1)基本思路
既然要求从叶子节点到树根的路径长度不超过 d d d,考虑从叶子节点回溯,到达某一层节点如果路径长度超过 d d d时,我们可以选择删除这个节点或其子节点。
删除这个节点而不是其子节点显然是最优的。
简单证明见下。
(2)贪心算法正确性
[1]最优子结构性质
对于森林而言,如果要求整体满足为 d d d森林且删除的顶点数量最少,应该使得其每一个子森林删除的顶点数量最少。所以显然 d d d森林问题满足最优子结构性质。
[2]贪心选择正确性证明
从叶子节点回溯,到达某一层的节点如果路径长度超过 d d d时,假设我们删除这个节点的子节点,
那么如果从另一个叶子节点另一条路径回溯路径长度超过 d d d,
我们还需要删除另一个节点。也即是,对于这个节点的子节点而言:
我们可能会删除 2 2 2个以上的节点。
如果我们直接删除这个节点,那么这一个点的子节点无需被删除
只需要删除一次超过 d d d的这一个节点即可。
(3)算法设计
从叶子节点回溯,到达某一层节点如果路径长度超过 d d d时,删除这个节点
3.算法实现
基于以上的分析,得到算法实现如下:
[1]非递归版本
#include<iostream>
using namespace std;
class dTree
{
int res;
//记录是否被删除
bool st[10010];
//输入的d,n
int d, n;
//统计入度
int deg[10010];
//手动实现的队列
int tt, hh;
int* q;
//结点信息
struct node
{
//父节点
int father;
//到父节点的距离
int dist;
//子节点中距离最大值
int w;
}nodes[10010];
public:
dTree(int n, int d)
{
this->d = d;
this->n = n;
//初始化度和是否被删除的信息
for (int i = 0; i <= n; i++)
deg[i] = 0, st[i] = false;
//初始化队列
tt = -1, hh = 0, res = 0;
q = new int[50000];
}
//添加边的信息
void add(int a, int b, int w)
{
nodes[b] = { a, w ,0 }, (deg[a])++;
}
//叶子结点回溯函数
void back()
{
for (int i = 0; i < n; i++)
{
//叶子节点入队
if (!deg[i])
{
q[++tt] = i;
}
}
//当队列不空
while (hh <= tt)
{
int t = q[hh++];//取出队头元素
if (!t)continue;
int father = nodes[t].father;//父亲结点
int dist = nodes[t].dist;//到父结点的距离
int w = nodes[t].w;//到所有子结点距离最大值
if (!st[father])
{
if (dist + w > d)
{
st[father] = true;
father = nodes[father].father;
res++;
}
else
{
int& x = nodes[father].w;
x = max(x, dist + w);
}
}
if (--(deg[father]) == 0)
q[++tt] = father;
}
}
void solution()
{
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
while (num--)
{
int b, c;
cin >> b >> c;
add(i, b, c);
}
}
back();
cout << res << endl;
}
~dTree()
{
if(q)delete[] q;
}
};
int main()
{
int n, d; //n为顶点个数,d为路径长度
cin >> n >> d;
dTree dt(n, d); //构建与初始化树
dt.solution(); //通过solution函数输出结果
return 0;
}
[2]递归版本
#include <bits/stdc++.h>
using namespace std;
class dTree
{
int n, d;
int ans;//保存答案
//邻接表
int e[10010], ne[10010], w[10010], h[10010], idx;
//表示是否被删除
bool st[10010];
public:
dTree(int n, int d)
{
//此处为构造函数,初始化构建树。
this->n = n;
this->d = d;
idx = ans = 0;
//初始化邻接表和st
for (int i = 0; i <= n; i++)
{
h[i] = -1;
st[i] = false;
}
}
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dfs(int u)
{
//dfs搜索求这一路径上的最长长度
int res = 0;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
int dist = dfs(j);//子树的最大长度
dist = max(dist + w[i], 0);
if (dist > d && !st[u])
{
st[u] = true;
ans++;
}
res = max(res, dist);
}
if (st[u])return -2e9;
return res;
}
void solution()
{
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
while (num--)
{
int b, c;
cin >> b >> c;
add(i, b, c);
}
}
dfs(0);
cout << ans << endl;
}
};
int main() {
int n, d; //n为顶点个数,d为路径长度
cin >> n >> d;
dTree dt(n, d); //构建与初始化树
dt.solution(); //通过solution函数输出结果
return 0;
}
4.数据测试
链接: d d d森林问题测试点