题目大意:
有一棵树,在树上每一步 可以跳k条边。设f(s, t)表示从s到t的最小步数。求所有点对的f值之和。
先考虑k = 1的情况,那么就是求树上任意两点的距离之和,相当于求每一条边被经过的次数,答案应该就是任意一条边左右两边点数的乘积之和。
然后如果k != 1,那么在一条路径长度不被k整除的时候就会出现问题,因为有一个余数,这个余数我们需要再走一步。
处理这个余数的问题,我们转化一下,可以先把这个长度补上一个数使得被k整除,然后我们就按照k = 1来计算答案,最后答案再除以k就行。
具体转移的时候,我们可以把u看做两个点的最近公共祖先,枚举一下子树内节点的深度来计算。
记f[i][j]表示以i为根的子树内深度%k = j的节点的个数,sz[i]表示i子树的大小,ans记录答案,然后转移代码大致如下,其中v是u的儿子,depth是u的深度:
rep(a, 0, K-1)
rep(b, 0, K-1){
int dis = ((a+b)%K - 2*depth%K + K)%K;
ans += ((K-dis)%K)*f[u][a]*f[v][b];
}
完整代码:
/*************************************************************
Problem: codeforces 790B - Bear and Tree Jumps
User: fengyuan
Language: C++
Result: Accepted
Time: 187 ms
Memory: 30200 KB
Submit_Time: 2017-11-17 21:43:43
*************************************************************/
#include<cstdio>
#include<cstring>
#include<iostream>
#define rep(i, x, y) for (int i = (x); i <= (y); i ++)
#define down(i, x, y) for (int i = (x); i >= (y); i --)
using namespace std;
typedef long long LL;
const int N = 200010;
LL ans = 0;
int n, K, cnt = 0;
int head[N];
LL f[N][10], sz[N];
struct Edge{
int to, nex;
}e[N<<1];
inline void add(int x, int y)
{
e[++ cnt].to = y;
e[cnt].nex = head[x];
head[x] = cnt;
}
inline void dfs(int u, int fa, int depth)
{
sz[u] = f[u][depth%K] = 1;
for (int i = head[u]; i; i = e[i].nex){
int v = e[i].to;
if (v == fa) continue;
dfs(v, u, (depth+1)%K);
rep(a, 0, K-1)
rep(b, 0, K-1){
int dis = ((a+b)%K - 2*depth%K + K)%K;
ans += ((K-dis)%K)*f[u][a]*f[v][b];
}
rep(j, 0, K-1) f[u][j] += f[v][j];
ans += sz[v]*(n - sz[v]);
sz[u] += sz[v];
}
}
int main()
{
scanf("%d%d", &n, &K);
rep(i, 1, n-1){
int x, y; scanf("%d%d", &x, &y);
add(x, y); add(y, x);
}
dfs(1, 0, 0);
cout << ans / K << endl;
return 0;
}