把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【题解】P3565 [POI2014]HOT-Hotels - 树形 $dp$

P3565 [POI2014]HOT-Hotels

声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。

题目描述

给定一棵树,在树上选 \(3\) 个点,要求两两距离相等,求方案数。

\(\\\)


\(\\\)

\(Solution\)

由于边权不为负(这道题里都为单位长),所以画画图可以发现三个点必定是以下两种形态( \(1,3,4\)\(5,7,8\)

其实这是一种,只不过树的形态不一样,根节点也就不一样了

简而言之,就是三个点中间一定有一个过渡点,证明很简单多画几个图就好了

根据这个图首先我们可以很快想到设 \(f[i][j]\) 为第 \(i\) 个点为根的子树中到 \(i\) 距离为 \(j\) 的儿子个数

然后呢?你会不会想到再设一个 \(g[i][j]\) 表示从 \(i\) 结点往上走 \(j\) 步可到达的点数?(不管你会不会反正我这样想了 \(233\)

这样子设貌似是可行的,而且也很好计算答案,但是细想会发现一个问题——两个儿子的最短距离不一定经过了 \(i\)

显然,儿子们之间的最短距离和 \(LCA\)千丝万缕密不可分的关系

于是我们把 \(g[i][j]\) 状态的意义改变一下,变成 以 \(i\) 为根节点的子树中,有多少个二元组 \((x, y)\) 满足 \(x, y\)\(LCA(x, y)\) 的距离都为 \(d\) ,且 \(LCA(x, y)\)\(i\) 的距离为 \(d - j\) ,其中 \(d\) 可以取任意值

用通俗的方式翻译一下就是,有多少对儿子,到他们的 \(LCA\) 的距离减去 \(LCA\)\(i\) 的距离为 \(j\)

为什么要这样设呢?我认为有两点

\(1.\) 可以方便地统计答案(后面再说

\(2.\) 转移比较方便,对于每个儿子的子树,只用继承 \(g[i][j] = \sum g[son][j + 1]\),对于不同的儿子则按照正常树形dp操作进行合并

最后解决——统计答案!

对于每个 \(i\) 分两种情况

\(1.\)\(g[i][j']\) 中取两个点,\(f[son][j]\) 中取一个点

则有 \(d * 2 = d + d - j' + 1 + j\) ,解得 \(j' = j + 1\)

\(ans += g[i][j + 1] * f[son][j]\)

\(2.\)\(f[i][j']\) 中取一个点,\(g[son][j]\) 中取一个点

则有 \(d * 2 = d + d - j + 1 + j'\) ,解得 \(j' = j - 1\)

\(ans += f[i][j - 1] * g[son][j]\)

完结撒花✿✿ヽ(°▽°)ノ✿

\(\\\)


\(\\\)

\(Code\)

这道题空间只有 \(62.5MB\) qwq,\(5000*5000\) 的数组开不下,我弄了一个类似手写栈玄学的东西,菊花图是可以卡掉(可见这道题数据过水233,不过还是顺利A啦

#include<bits/stdc++.h>
#define ll long long
#define F(i, x, y) for(int i = x; i <= y; ++ i)
using namespace std;
int read();
const int N = 5e3 + 5;
const int M = 3e3 + 5;
int n, u, v;
int tot, top, gar[N], num[N];
int head[N], cnt, ver[N << 1], nxt[N << 1];
ll ans, f[M][M], g[M][M];
void add(int x, int y){ver[++ cnt] = y, nxt[cnt] = head[x], head[x] = cnt;}
int kk()//相当于给它一个固定的数组编号
{
	if(gar[top]) return gar[top --];
	return ++ tot;
}
void dp(int x, int fa)
{
	num[x] = kk(), f[num[x]][0] = 1;
	for(int i = head[x]; i; i = nxt[i])
	{
		int son = ver[i], maxh = 1;
		if(son == fa) continue;
		dp(son, x);
		for(int j = 1; f[num[son]][j]; ++ j) maxh = j + 1;
                //注意树形dp更新顺序
		F(j, 0, maxh) ans += g[num[x]][j + 1] * f[num[son]][j] + f[num[x]][j] * g[num[son]][j + 1];
		F(j, 1, maxh) g[num[x]][j] += f[num[x]][j] * f[num[son]][j - 1];
		F(j, 0, maxh) g[num[x]][j] += g[num[son]][j + 1];	
		F(j, 1, maxh) f[num[x]][j] += f[num[son]][j - 1];
		memset(f[num[son]], 0, sizeof(f[num[son]]));
		memset(g[num[son]], 0, sizeof(g[num[son]]));
		gar[++ top] = num[son];//这个儿子的所有贡献计算完了,可以把所有数据丢弃,回收它的数组编号
	}
}
int main()
{
	n = read();
	F(i, 1, n - 1) u = read(), v = read(), add(u, v), add(v, u);
	dp(1, 0), printf("%lld", ans);
	return 0;
}
int read()
{
	int x = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}
posted @ 2020-06-07 15:07  Bn_ff  阅读(195)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end