[CF791D]Bear and Tree Jumps 树上DP

题面:

给定一颗大小为 n n n的树,给定一个k,求 ∑ i = 1 n ∑ j = 1 n ⌈ d i s ( i , j ) k ⌉ \sum_{i=1}^n \sum_{j=1}^n \lceil \frac{dis(i,j)}{k} \rceil i=1nj=1nkdis(i,j)

范围&性质: 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ k ≤ 5 1\le n \le 2\times 10^5,1\le k\le 5 1n2×105,1k5

分析:

朴素暴力做法:

对于每一个点dfs一遍,统计答案,复杂度 O ( n 2 ) O(n^2) O(n2)

正解:(前排膜拜 Orz)

先考虑k=1怎么做,相当于求树上任意两点间路径和,直接枚举每条边,求出左右两部分内点的个数 n u m 1 , n u m 2 num1,num2 num1,num2,相乘就好了,复杂度 O ( n ) O(n) O(n)

那怎么向k>1扩展呢,问题在于k>1时路径长度除以k会有余数,那我们就考虑把余数补齐,答案就变成了 a n s + ∑ f ( i , j ) k \frac{ans+\sum f(i,j)}{k} kans+f(i,j),其中 f ( i , j ) f(i,j) f(i,j)表示将 d i s ( i , j ) dis(i,j) dis(i,j)补成k的倍数需要的代价,这样就可以消掉上取整符号的影响了

那么问题就转化成了如何求 ∑ f ( i , j ) \sum f(i,j) f(i,j),这时候就利用到k很小这一性质,我们记 d p [ i ] [ j ] dp[i][j] dp[i][j]表示u的子树内到根节点距离 k k k j j j的点的个数,对于树上任意两点 a , b a,b a,b而言,他们之间的距离 d i s ( a , b ) = d e p [ a ] + d e p [ b ] − 2 ∗ d e p [ l c a ] dis(a,b)=dep[a]+dep[b]-2*dep[lca] dis(a,b)=dep[a]+dep[b]2dep[lca] f ( a , b ) = ( d i s ( a , b ) − k ) m o d   k f(a,b)=(dis(a,b)-k)mod \ k f(a,b)=(dis(a,b)k)mod k,转移的时候我们就可以枚举 u , v u,v u,v中dp状态第二维 i , j i,j i,j,这些点之间的距离 d i s = ( i + j − d e p [ u ] ) m o d   k dis=(i+j-dep[u]) mod\ k dis=(i+jdep[u])mod k(因为 u , v u,v u,v l c a lca lca u u u)单个点对需要的代价 f = ( d i s − k ) m o d   k f=(dis-k)mod\ k f=disk)mod k,由乘法原理得总的代价为 f ( i , j ) × d p [ u ] [ i ] × d p [ v ] [ j ] f(i,j)\times dp[u][i]\times dp[v][j] f(i,j)×dp[u][i]×dp[v][j]

代码:

#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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值