题目描述
A tree is an undirected connected graph without cycles. The distance between two vertices is the number of edges in a simple path between them.
Limak is a little polar bear. He lives in a tree that consists of n vertices, numbered 1 through n.
Limak recently learned how to jump. He can jump from a vertex to any vertex within distance at most k.
For a pair of vertices (s, t) we define f(s, t) as the minimum number of jumps Limak needs to get from s to t. Your task is to find the sum of f(s, t) over all pairs of vertices (s, t) such that s < t.
题目大意
算出各个节点之间距离和,答案是ans/k的向上取整。
40分解法
一股脑直接上一个\(dfs\),枚举每一个节点为根节点的情况,做\(n\)遍\(dfs\)。
40分代码
#include<bits/stdc++.h>
#define LL long long
#define inf
#define N 150005
using namespace std;
struct edge{int to,nt;}E[N<<1];
int H[N<<1],n,cas,k,cnt=0;
char s[1000];
LL ans=0;
int a,b,c,id;
int r(){int x=0,w=0;char ch=0;while(!isdigit(ch))w|=ch=='-',ch=getchar();while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return w?-x:x;}
void addedge(int u,int v){E[++cnt]=(edge){v,H[u]};H[u]=cnt;E[++cnt]=(edge){u,H[v]};H[v]=cnt;}
void dfs(int u,int fa,int dis,int an){
for(int i=H[u];i;i=E[i].nt){
int v=E[i].to;if(v==fa)continue;
ans+=ceil((1.0*dis+1)/(1.0*k));
dfs(v,u,dis+1,an);
}
}
int main(){
cas=r(),n=r(),k=r();
for(int i=1;i<n;i++)addedge(r(),r());
for(int i=1;i<=n;i++)dfs(i,-1,0,i);
printf("%lld\n",ans/2);
return 0;
}
100分解法
我们思考一下,对于树上的一条边\(edge\),假设\(edge\)两边的点数分别是\(sum1\)和\(sum2\),那么我们可以知道通过这条边的路径一共有\(sum1*sum2\),乘法原理大家都明白。
那么我们就知道了全部的路径,但是这个是可以隔着跳的,那么我们就必须考虑有多出来的节点,也就是让当前距离+dist(我们计算多出来的距离)/k,才是我们的答案。
这样我们就把问题分成了两个部分,一个是算出\(ans\),也就是\(sum1\)和\(sum2\)乘机的和,还有一部分是\(sum\)表示的是需要我们将路径凑整的距离之和。
答案就变成了\((ans+sum)/k\)。
那么我们就考虑树上两点之间的距离是len=dep[u]+dep[v]-2dep[root],其中我们的root表示u节点和v节点的最近公共祖先,但是这道题的树形DP中,我们的v是属于u的儿子,那么这个距离不需要用LCA倍增做法来求,因为这个祖先就是u。
计算sum的方法:dp[i][j]表示到i点的距离对k取摸为j的点的总数。
转移我们需要枚举长度i,j(i和j都是mod的余下的距离)那么我们就要考虑这些点之间需要多跳的距离,首先算出这些点距离的余数是len=(i+j-2dep[u])%k,那么sum=(len-k)%k,因为我们要往大的跳,用乘法原理就是求出当前这个距离之间的点所有的点需要多跳的距离是lenf[u][a]f[v][b],这里非常好理解。
在说一下,我们在做这个树上dp的思路还是针对这个中间的edge。
下面说一下为什么在转移的时候为什么是直接f[u][a]+=f[v][a],这是因为我们基于通过(u,v)中间着一条边来处理,所以这个余数不需要考虑,也为已经考虑过了。
总的时间复杂度就是O(n*k^2),非常的优美。(一道树形dp的好题)
100分代码
#include<bits/stdc++.h>
#define N 200005
#define LL long long
using namespace std;
struct edge{int to,nt;}E[N<<1];
int cnt,n,k,H[N];
LL f[N][10],ans,sz[N];
int r(){int w=0,x=0;char ch=0;while(!isdigit(ch))w|=ch=='-',ch=getchar();while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return w?-x:x;}
void addedge(int u,int v){E[++cnt]=(edge){v,H[u]};H[u]=cnt;E[++cnt]=(edge){u,H[v]};H[v]=cnt;}
void dfs(int u,int fa,int dep){
sz[u]=f[u][dep%k]=1;//计算到根节点的距离
for(int i=H[u];i;i=E[i].nt){//枚举所有相邻的边
int v=E[i].to; if(v==fa)continue;//是父亲就重来
dfs(v,u,dep+1);//向下递归算子树
for(int a=0;a<k;a++)//枚举第一个距离
for(int b=0;b<k;b++){//枚举第二个距离
int dis=(a+b-dep*2)%k;//求出距离
int rev=(k-dis)%k;//表示这里的每一个节点都需要多跳rev的距离
ans+=rev*f[u][a]*f[v][b];//运用乘法原理求出当前的sum
}
sz[u]+=sz[v];//将儿子v的大小赋值给u
for(int a=0;a<k;a++)f[u][a]+=f[v][a];//计算f数组。
ans+=sz[v]*(n-sz[v]);//算出当前的ans
}
}
int main(){
n=r(),k=r();
for(int i=1;i<n;i++)addedge(r(),r());
dfs(1,-1,0);printf("%lld\n",ans/k);
return 0;
}