您的位置:首页 > 其它

【BZOJ2152】聪聪可可,dfs+递推/点分治

2016-04-26 14:39 253 查看
传送门

写在前面:╯﹏╰

思路:刚开始char哥在做这个题,提供了不用点分治而是树上递推的做法,然后我就先调出来了……

对树进行dfs,f[u][y]表示以x为根的子树中,与u距离为y的点(mod 3),g[u]为以x为根的子树中符合条件的点对(不包含(1,1),(2,2)……),那么最后答案就是((g[1]+n)/gcd,n∗n/gcd)

f的递推式还是比较好想的

f[u][w(u,v)+y]=∑f[v][y]v为x的儿子

g的递推式有些细节,首先在统计3种点对(在mod3情况下为0,1,2)时,分成LCA为u的点对和LCA不为u的点对(实际上就是一棵子树中原本有的点对和两个子树间的点对)

前者直接加g[v]就好了

后者则是利用乘法原理,比如第一棵子树中与u距离为1(mod 3)的点有3个,其他子树(不能包含第一棵子树中的点,因为已经在dfs第一棵子树的时候统计过了,我们要求的是LCA不为u的点对!)中与u距离为2的点有2个,那么g[u]+=2*3,同时值得注意的是,在计算距离为0的点时,u也是要算进去的,但计算一次子树时它只能算一次(好可怜啊),其他点在循环后都算了两次的,所以我们计算时要加个一

注意:都说过了……

代码:

#include<bits/stdc++.h>
using namespace std;
int tot,g[20010],f[20010][3],first[20010],n,x,y,z;
int fa[20010];
struct edge
{
int u,v,w,next;
}e[40010];
void add(int x,int y,int z)
{
e[++tot].u=x;
e[tot].v=y;
e[tot].w=z;
e[tot].next=first[x];
first[x]=tot;
}
void dfs(int x)
{
for (int i=first[x];i;i=e[i].next)
if (fa[x]!=e[i].v)
{
fa[e[i].v]=x;
dfs(e[i].v);
f[x][e[i].w]+=f[e[i].v][0];
f[x][(e[i].w+1)%3]+=f[e[i].v][1];
f[x][(e[i].w+2)%3]+=f[e[i].v][2];
}
for (int i=first[x];i;i=e[i].next)
if (fa[x]!=e[i].v)
{
g[x]+=g[e[i].v];
g[x]+=(f[x][0]-f[e[i].v][(3-e[i].w)%3]+1)*f[e[i].v][(3-e[i].w)%3];//这就是我们刚才说的问题,(u,x)和(x,u)都是要算的
g[x]+=(f[x][1]-f[e[i].v][(4-e[i].w)%3])*f[e[i].v][(5-e[i].w)%3];
g[x]+=(f[x][2]-f[e[i].v][(5-e[i].w)%3])*f[e[i].v][(4-e[i].w)%3];
}
}
int gcd(int x,int y)
{
if (!y) return x;
return gcd(y,x%y);
}
main()
{
scanf("%d",&n);
for (int i=1;i<n;i++)
scanf("%d%d%d",&x,&y,&z),
z%=3,add(x,y,z),add(y,x,z);
for (int i=1;i<=n;i++) f[i][0]=1;
dfs(1);
int p=gcd(g[1]+n,n*n);
printf("%d/%d",(g[1]+n)/p,n*n/p);
}


附样例

6
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
7/18

9
1 2 1
1 3 2
2 4 1
2 5 2
4 6 1
4 7 2
4 8 0
3 9 1
11/27

5
1 2 1
1 3 2
2 4 1
2 5 2
11/25

3
1 2 0
2 3 0
1/1


在没学过一个算法时,我们并不要畏惧它,甚至连看都不敢看(至少想想如果在考场上遇到,暴力该怎么打吧……)另辟蹊径,也能走出一片天地
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: