题意
对于一棵 n n n 个点的树,求其点分治方案数。
两种点分治方案不同,当且仅当某个连通块的分治中心不同。
为了避免出现歧义,我们提供了一份暴力代码来具体描述这个算法。
const int mod=1e9+7;
const int maxn=5005;
bool vis[maxn];
vector<int> e[maxn];
int n;
inline void view_all(vector<int> &cur,int x,int fa){
cur.push_back(x);
for(int p: e[x]){
if (vis[p]) continue;
if (p == fa) continue;
view_all(cur, p, x);
}
}
inline int calc(int x){
vector<int> cur;
int ans = 0;
view_all(cur, x, -1);
for(int w: cur){
int res = 1;
vis[w] = 1;
for(int p: e[w]){
if (vis[p]) continue;
res = 1ll * res * calc(p) % mod;
}
vis[w] = 0;
ans = (ans + res) % mod;
}
return ans;
}
inline int get_ans(){
return calc(1);
}
对 10 % 10\% 10% 的数据, n ≤ 5 n\le 5 n≤5.
对 40 % 40\% 40% 的数据, n ≤ 50 n\le 50 n≤50.
对另 20 % 20\% 20% 的数据,树为一条链.
对 100 % 100\% 100% 的数据, n ≤ 5000 n\le 5000 n≤5000.
题解
考虑树形DP,问题在于两个已经确定点分树的形态的树,合并后能形成多少种形态的点分树。
但这是一个未定义的问题,先考虑定义两颗原本相连的树
u
,
v
u,v
u,v分裂后,它们各自的点分树应该是什么样的:在点分树上把在原来的树上属于
u
,
v
u,v
u,v的点黑白染色,每个点找它同色的最近祖先,就唯一地得到两个新的点分树。
考虑有多少点分树经过这样的操作后能得到原来
u
,
v
u,v
u,v的点分树,首先原树中相邻的节点在点分树中一定有祖先后代关系,而原来点分树中
u
,
v
u,v
u,v到根的路径上的其他子树合并后形态还是确定的(因为要保证原来的祖先后代关系),不妨先把它缩起来,这样剩下
u
,
v
u,v
u,v到根的路径上的点,可以以任意次序合并。
所以只需知道
u
u
u在原来的点分树上的深度,记
f
[
u
]
[
i
]
f[u][i]
f[u][i]为
u
u
u在原来的点分树上深度为
i
i
i的方案数,按树形背包的方式合并,前缀和优化后可做到每次的效率都是两子树大小乘积,复杂度
O
(
n
2
)
O(n^{2})
O(n2)。
本题的突破口在于从分裂的情况(多个映射到一个)出发,定义合并时的方案数(一个对应多个),使得所有方案得以不重不漏地计算到。