洛谷 P2634 [国家集训队]聪聪可可 树形DP 题解

这是我第一篇用markdown写的博客,格式不好请见谅。

每日一题 day63 打卡

Analysis

这道题正解是点分治,但我发现了树形DP的做法,于是我就写了树形DP。

\[dp[i][0/1/2]表示i的子树中有多少个点与i距离模3余数为0,1,2 \]

首先思考如何转移,对于每个i来说,可以用dp[from][(j+val)%3]+=dp[to][j] (j为枚举的情况)来更新根节点的答案,因为每一个子树中的距离在新的根中都会+val

接下来思考如何记录答案,用乘法原理计算当前子树的状态与之前算过的所有子树的状态会形成多少合法解,即ans+=dp[to][j]\(\times\) dp[from][((-j-val)%3+3)%3]\(\times\) 2 。这时,作为读者的你一定会问,为什么是((-j-val)%3+3)%3? 因为是当前子树的状态与之前算过的所有子树的状态且要满足(x+y)%3=0,所以应该是3-j-val3可以约掉,\(j>0\&val>0\) ,得出((-j-val)%3+3)%3

注意事项:

1.因为有可能只有一个子树,所以将dp[from][0]=1来避免这种情况。

2.因为数对(两个人选的点)有序,所以在计算答案时\(\times 2\)

3.因为两个点重合时答案合法,所以最后要将\(ans+=n\)

4.输出时别忘了约分。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#define maxn 20000+10
#define rep(i,s,e) for(register int i=s;i<=e;++i)
#define dwn(i,s,e) for(register int i=s;i>=e;--i)
using namespace std;
inline int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar();}
	while(c>='0'&&c<='9') {x=x*10+c-'0'; c=getchar();}
	return f*x;
}
void write(int x)
{
	if(x<0) {putchar('-'); x=-x;}
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int n,cnt,ans;
int head[maxn];
int dp[maxn][3];
struct node
{
	int v,w,nex;
}edge[2*maxn];
inline void add(int x,int y,int z)
{
	edge[++cnt].v=y;
	edge[cnt].w=z;
	edge[cnt].nex=head[x];
	head[x]=cnt;
}
void tree_dp(int from,int father)
{
	dp[from][0]=1;
	for(int i=head[from];i;i=edge[i].nex)
	{
		int to=edge[i].v,val=edge[i].w%3;
		if(to==father) continue;
		tree_dp(to,from);
		rep(j,0,2)
			ans+=dp[to][j]*dp[from][((-j-val)%3+3)%3]*2;
		rep(j,0,2)
			dp[from][(j+val)%3]+=dp[to][j];
	}
}
signed main()
{
	n=read();
	rep(i,1,n-1)
	{
		int x=read(),y=read(),z=read();
		add(x,y,z);
		add(y,x,z);
	}
	tree_dp(1,-1);
	ans+=n;
	int fz=ans,fm=n*n;
	write(fz/__gcd(fz,fm));
	putchar('/');
	write(fm/__gcd(fz,fm));
	return 0;
}

如有失误请各位大佬斧正(反正我不认识斧正是什么意思)

posted @ 2019-12-28 13:43  handsome_zyc  阅读(151)  评论(0编辑  收藏  举报