树形DP
题意:
有一棵n个节点的树,现在定义一个函数:s(i,j),i<j,意义是i节点和j节点的距离。 距离是根据一个跳的次数而定,一个人从i节点每次最多跳k次,而其跳的次数便是距离。
思路:
一般在树上计算距离或者其它运算的问题,很难离开树形dp的范畴,但是关键在于如何转化问题为结果。
这里说i < j ,其实意味着i到j只需走一次即可。数据范围是200000,暴力枚举每一个节点是不行的,那么可不可以直接一次dfs求出想要的答案?对于一棵树而言,其中的某个节点有一个或者多个儿子,dp问题是 从子结构到最优解,那么能不能先只求出一个非叶子节点的儿子之间的所有距离,可以的。
分析距离的计算方式发现是k的倍数会多跳一步,对于一个节点来说,其某一个儿子树到这个节点的其它儿子树的距离有两种情况:1 . 倍数 2. 除以k有余数的情况。 对于第一种直接保存在一个数组中,重点在于重复保存,这样可以直接当做跳一次就行,比如:k = 2的时候有一个儿子节点的到这个节点的距离是4,但是我们不直接拿着4/2而是保存两次倍数即可,算的时候直接做乘法,求出当前儿子树的倍数个数乘以父亲节点和已经算过的其它父亲节点儿子的个数,当然还要加上其它父亲的儿子节点的倍数关系乘以当前儿子的个数。
那么还有一种情况是不是倍数关系但是到当前节点的距离加上到其儿子节点的距离大于k,那么需要加一次计算即可。
当算出一个儿子树就与父亲节点合并起来,最终的答案就是解。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int maxn = 200005;
int n,k;
LL dp[maxn][6],ans;
LL segment[maxn],sonNumber[maxn];
vector<int>G[maxn];
void dfs(int u,int fa)
{
sonNumber[u] = 1;
dp[u][0] = 1;
int Size = G[u].size();
for(int i = 0;i < Size; i++) {
int to = G[u][i];
if(to == fa) continue;
dfs(to,u);
ans += segment[to]*sonNumber[u] + segment[u]*sonNumber[to];
for(int i = 0;i < k; i++) {
for(int j = 0;j < k; j++) {
ans += dp[u][i]*dp[to][j];
if(i+j >= k)
ans += dp[u][i]*dp[to][j];
}
}
sonNumber[u] += sonNumber[to];
segment[u] += segment[to];
segment[u] += dp[to][k-1];
for(int i = 0;i < k; i++)
dp[u][(i+1)%k] += dp[to][i];
}
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&k);
for(int i = 1;i < n; i++) {
int e,s;
scanf("%d%d",&e,&s);
G[e].push_back(s);
G[s].push_back(e);
}
ans = 0;
dfs(1,0);
printf("%I64d\n",ans);
return 0;
}