【题解】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;
}