[CF791D]Bear and Tree Jumps 樹上DP
阿新 • • 發佈:2020-09-16
題面:
給定一顆大小為\(n\)的樹,給定一個k,求\(\sum_{i=1}^n \sum_{j=1}^n \lceil \frac{dis(i,j)}{k} \rceil\)
範圍&性質:\(1\le n \le 2\times 10^5,1\le k\le 5\)
分析:
樸素暴力做法:
對於每一個點dfs一遍,統計答案,複雜度\(O(n^2)\)
正解:(前排膜拜 Orz)
先考慮k=1怎麼做,相當於求樹上任意兩點間路徑和,直接列舉每條邊,求出左右兩部分內點的個數\(num1,num2\),相乘就好了,複雜度\(O(n)\)
那怎麼向k>1擴充套件呢,問題在於k>1時路徑長度除以k會有餘數,那我們就考慮把餘數補齊,答案就變成了 \(\frac{ans+\sum f(i,j)}{k}\)
那麼問題就轉化成了如何求\(\sum f(i,j)\),這時候就利用到k很小這一性質,我們記\(dp[i][j]\)表示u的子樹內到根節點距離模\(k\)餘\(j\)的點的個數,對於樹上任意兩點\(a,b\)而言,他們之間的距離\(dis(a,b)=dep[a]+dep[b]-2*dep[lca]\),\(f(a,b)=(dis(a,b)-k)mod \ k\),轉移的時候我們就可以列舉\(u,v\)中dp狀態第二維\(i,j\),這些點之間的距離\(dis=(i+j-dep[u]) mod\ k\)
程式碼:
#include<bits/stdc++.h> using namespace std; namespace zzc { const int maxn = 200005; int cnt=0,head[maxn]; long long dp[maxn][5],siz[maxn],ans=0; int k,n; struct edge { int to,nxt; }e[maxn<<1]; void add(int u,int v) { e[++cnt].to=v; e[cnt].nxt=head[u]; head[u]=cnt; } void dfs(int u,int fa,int dep) { dp[u][dep%k] = 1;siz[u]=1; for (int i=head[u];i;i=e[i].nxt) { int v=e[i].to; if (v==fa) continue; dfs(v,u,dep+1); for (int j = 0;j < k;j++) { for (int r = 0;r < k;r++) { int dis = (j + r - dep * 2) % k; int rev = (k - dis) % k; ans += rev*dp[u][j] * dp[v][r]; } } siz[u]+=siz[v]; for (int j = 0;j < k;j++) dp[u][j] += dp[v][j]; ans += (n - siz[v])*siz[v]; } } void work() { scanf("%d%d",&n,&k); for (int i=1,x,y;i < n;i++) { scanf("%d%d",&x,&y); add(x,y);add(y,x); } dfs(1, -1, 0); printf("%lld\n",ans/k); } } int main() { zzc::work(); return 0; }