树状dp的应用

树状dp的应用

树的最大独立集、重心、最长点对是常见的问题

树的重心

树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。
例题:黑帮老大
题目描述: 城里有一个黑手党组织。把黑手党的人员关系用一棵树来描述,教父是树的根,每个结点是一个黑手党徒。为了保密,每人只和他的父结点和他的子结点联系。警察知道哪些人互相来往,但是不知他们的关系。警察想找出谁是教父。
   警察假设教父是一个聪明人:教父懂得制衡手下的权力,所以他直属的的几个小头目,每个小头目属下的人数差不多。也就是说,删除根之后,剩下的几个互不连通的子树(连通块),其中最大的连通块应该尽可能小。帮助警察找到哪些人可能是教父。
输入格式:第一行是n,表示黑手党的人数,2 ≤ n ≤ 50 000。黑手党徒的编号是1到n。下面有n-1行,每行有2个整数,即有联系的2个人的编号。
输出格式:输出疑为教父的结点编号,从小到大输出。
输入样例:
6
1 2
2 3
2 5
3 4
3 6
输出样例:
2 3
在这里插入图片描述

本题中教父就是这课人际关系树的重心。
暴力法做就是将每个节点都删去一遍然后对于每个分离出来的子树进行DFS求总节点数,显然这种方法非常低效。
其实只用一遍的DFS就可以求出。

观察u,在删去u之后图被分为了包含2,包含3,包含1的三个树。
假设我们这棵树以1为根那么d [2]就是包含2的树的节点数,d [3]是包含3的树的节点数,d [u]=d [2]+d [3]+1,包含1的树的节点树就是n - d [u],这样我们就通过以1为根的树的节点数数组求出了以u为根时的子树节点数。所以我们任取一点为根就可以求出所有点为根的情况。

本题的n很大,用链式前向星存树能有效节省空间。

#include<iostream>
#include<algorithm>
using namespace std;

const int MAX = 50005;//节点最大数
struct Edge {
	int to, next;//to赋值是边的端点,next指向下一个边
}edge[MAX << 1];//无向边一个边存两次所以是两倍的最大边数

int head[MAX], cnt = 0;
//head作用类似头指针,cnt是数据输入时用的“指针”
void init()
{
	for (int i = 0; i < MAX; i++)
	{
		edge[i].next = -1;//边界用-1指示
		head[i] = -1;
	}
	cnt = 0;
}

void addedge(int u, int v)
{
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt++;//将新的边数据插入链表头
}

int n;
int d[MAX], ans[MAX], num = 0, maxnum = 1e9;

void dfs(int u, int father)
{
	d[u] = 1;
	int tmp = 0;
	for (int i = head[u]; i != -1; i = edge[i].next)
	//i从头开始,到发现-1结束,利用next信息找到下一个边的下标
	{
		int v = edge[i].to;
		if (v == father)continue;

		dfs(v, u);

		d[u] += d[v];
		tmp = max(tmp, d[v]);
	}
	tmp = max(tmp, n - d[u]);

	if (tmp < maxnum)//发现更合适的重心
	{
		maxnum = tmp;
		num = 0;//初始化答案数组
		ans[num++] = u;
	}
	else if (tmp == maxnum)//发现多个疑似的重心
	{
		ans[num++] = u;
	}

}

int main()
{
	init();
	cin >> n;
	int u, v;
	for (int i = 0; i < n-1; i++)
	{
		cin >> u >> v;
		addedge(u, v);
		addedge(v, u);
	}

	dfs(1, 0);

	sort(ans , ans + num);
	for (int i = 0; i < num; i++)
	{
		cout << ans[i] << ' ';
	}
	cout << endl;

	system("pause");
	return 0;
}

树的最大独立集

对于一棵有N个结点的无根树,选出尽量多的结点,使得任何两个结点均不相邻(称为最大独立集)。

由于每个点只由其儿子或者孙子决定(二者的最大值),所以我们可以深搜一遍,回溯的时候用当前节点更新其父亲以及父亲的父亲(因为此时该节点的值已经被我们计算出来了),这种由已知贡献给未知的方法称为刷表法。

#include<iostream>
#include<algorithm>
using namespace std;

const int MAX = 100;
struct edge {
	int to, next;
}edge[MAX<<1];

int head[MAX], s[MAX], gs[MAX], d[MAX], fa[MAX], gfa[MAX];
int cnt = 0, n;

void init()
{
	for (int i = 0; i < MAX; i++)
	{
		edge[i].next = -1;
		head[i] = -1;
	}
	cnt = 0;
}

void addedge(int u, int v)
{
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}

void dfs(int u, int father)
{
	gfa[u] = fa[fa[u]];
	for (int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == father)continue;
		fa[v] = u;

		dfs(v, u);
	}
	d[u] = max(s[u], gs[u] + 1);

	s[fa[u]] += d[u];
	gs[gfa[u]] += d[u];
}

int main()
{
	init();
	memset(fa, 0, sizeof(fa));
	memset(gfa, 0, sizeof(gfa));
	cin >> n;
	int u, v;
	for (int i = 1; i < n; i++)
	{
		cin >> u >> v;
		addedge(u, v);
		addedge(v, u);
	}

	dfs(1, 0);
	
	cout << d[1] << endl;
	system("pause");
	return 0;
}

树的最长点对(直径)

一棵无根树距离最远的两个点称为树的最长点对(这个距离可以是两点之间的树枝数,在树枝给出距离的情况下是总距离最长的一段)

先任取一点作为跟dfs遍历整个数,找出与这个根最远的点,再以这个点为根在计算一次,得出的就是最长点对。

#include<iostream>
#include<algorithm>
using namespace std;

const int MAX = 100;
struct edge {
	int to, next;
}edge[MAX << 1];

int head[MAX], d[MAX];
int cnt = 0, n, max_num = 0;

void init()
{
	for (int i = 0; i < MAX; i++)
	{
		edge[i].next = -1;
		head[i] = -1;
	}
	cnt = 0;
}

void addedge(int u, int v)
{
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}

void dfs(int u, int father)
{

	for (int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == father)continue;

		d[v] = d[u] + 1;
		dfs(v, u);
	}
	if (d[u] > d[max_num])
		max_num = u;
}

int main()
{
	init();

	cin >> n;
	int u, v;
	for (int i = 1; i < n; i++)
	{
		cin >> u >> v;
		addedge(u, v);
		addedge(v, u);
	}

	dfs(1, 0);
	cout << max_num;
	dfs(max_num, 0);

	cout << ' ' << max_num << endl;

	system("pause");
	return 0;
}

树的直径是树上最长的一条链;

但是真正常考的是树的中点,也就是树的直径中点;

树的中点性质:以树的中心为整棵树的根时,从该根到每个叶子节点的最长路径最短

这个和重心是有区别的;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值