【bzoj3696】化合物 树形dp
题目描述
首长NOI惨跪,于是去念文化课了。现在,他面对一道化学题。
这题的来源是因为在一个奇怪的学校两个化竞党在玩一个奇怪的博弈论游戏。这个游戏很蛋疼,我相信你们也没有兴趣听。
由于这个游戏涉及博弈论,因此化竞的同学就要求首长求一个类似SG函数的值。
他们手中有一种非常神奇的化合物,它的分子由N个原子组成(不要在意一个原子可能和及其多个原子成键这个细节)。这个分子构成一个树结构,1号分子为根。 若两个原子i、j到它们的最近公共祖先的距离分别是Li和Lj,定义它们的Aij值为:
Aij=Li xor Lj
题目要求对于每一个k(k∈N),求出两两A值为k的原子对个数。
输入
第一行一个整数N。
接下来N-1行,每行一个整数p,第i行的整数表示第i个原子的父亲为p。
输出
从K=0开始,第k+1行输出两两A值为K的原子对个数,输出到第一个不为零的数为止。
样例输入
3
1
1
样例输出
1
2
题解
树形dp
第一眼看到这题感觉直接上树形dp可能会TLE,然而貌似数据水。
然后随便搞了搞就交上去了, 结果不但没T,而且跑得飞快(CQzhangyu压了压状态然后怒拿rank1)
设f[i][j]表示以i为根的子树中深度为j的节点个数。
考虑答案,枚举x中深度为j的节点和x的儿子s中深度为k的节点,那么显然$ans[(j-deep[x])xor(k-deep[x])]+=f[x][j]*f[s][k]$。
然后再更新x。
上界设的紧一点就可以稳切了。
时间复杂度大概是$O(nh^2)$的。
另外本题亲测不需要开long long
UPD:正解貌似是FWT,然而好像没有暴力快。。。
#include <cstdio> #include <cstring> #include <algorithm> #define N 100010 using namespace std; int head[N] , to[N] , next[N] , cnt , f[510][N] , deep[N] , md[N] , ans[520]; void add(int x , int y) { to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x) { int i , j , k; md[x] = deep[x] , f[deep[x]][x] ++ ; for(i = head[x] ; i ; i = next[i]) { deep[to[i]] = deep[x] + 1 , dfs(to[i]); for(j = deep[x] ; j <= md[x] ; j ++ ) for(k = deep[to[i]] ; k <= md[to[i]] ; k ++ ) ans[(j - deep[x]) ^ (k - deep[x])] += f[j][x] * f[k][to[i]]; for(j = deep[to[i]] ; j <= md[to[i]] ; j ++ ) f[j][x] += f[j][to[i]]; md[x] = max(md[x] , md[to[i]]); } } int main() { int n , i , x; scanf("%d" , &n); for(i = 2 ; i <= n ; i ++ ) scanf("%d" , &x) , add(x , i); dfs(1); for(i = 0 ; ans[i] ; i ++ ) printf("%d\n" , ans[i]); return 0; }