codeforces791D--Bear and Tree Jumps
題目描述
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]-2*dep[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;
}
codeforces791D--Bear and Tree Jumps