problem
给定一棵n个点的树,问其中有多少条长度为偶数的路径。路径的长度为经过的边的条数。x到y与y到x被视为同一条路径。路径的起点与终点不能相同。
Input
第一行一个数n表示点的个数;
接下来n-1行,每行两个整数x,y表示边;
保证输入数据形成一棵树;
1<=n<=100000
Output
一行一个整数表示答案。
Sample Input
3
1 2
1 3
Sample Output
1
思路
考虑tree DP,dp1【】数组表示以当前结点为根的子树中所有到根的路径长度为奇数的结点数
dp2【】数组表示以当前结点为根的子树中所有到根的路径长度为偶数的结点数。就有“父奇为儿偶,父偶为儿奇”
进入每个结点时,初始化为dp1[rt]=0,dp2[rt]=1;
就是说,“零距离”记为1个,这样在计数时,比如叶子节点的父节点,奇数就加上偶数(0+1=1),偶数就加上奇数(0+0=0),符合。
另外,最关键的一点在于 ans+=dp1[rt]*dp2[tt]+dp2[rt]*dp1[tt];
就是说,对当前这个根节点,有一个它的子树到他的奇 ∗ 另外一个子树的奇(奇+奇=偶)(或者说是已经合并的,实际上这就是一个合并信息到根的过程),和偶 ∗ 偶(偶+偶=偶),就是ans要加的值。
核心代码为
void dfs(int rt,int f)
{
dp1[rt]=0;
dp2[rt]=1;
for(int i=head[rt];i!=-1;i=edges[i].to){
int tt=edges[i].from;
if(tt==f) continue;
dfs(tt,rt);
ans+=dp1[rt]*dp2[tt]+dp2[rt]*dp1[tt];
dp1[rt]+=dp2[tt];//父奇为儿偶
dp2[rt]+=dp1[tt];//父偶为儿奇
}
}
代码示例
#include<iostream>
#include<string.h>
#include<cstdio>
using namespace std;
const int maxn=100010;
int head[maxn<<1];
int tot,n;//tot辅助存边(边集数组转邻接表),n为结点数
struct Edge{
int from,to;
}edges[maxn<<1];
void init()
{
memset(head,-1,sizeof(head));
tot=0;
}
void add_edge(int u,int v){
edges[tot].from=v;
edges[tot].to=head[u];
head[u]=tot++;
}
int dp1[maxn],dp2[maxn];
//1为奇路径方案数,2为偶路径方案数
long long ans=0;//答案
void dfs(int rt,int f)
{
dp1[rt]=0;
dp2[rt]=1;
for(int i=head[rt];i!=-1;i=edges[i].to){
int tt=edges[i].from;
if(tt==f) continue;
dfs(tt,rt);
ans+=dp1[rt]*dp2[tt]+dp2[rt]*dp1[tt];
dp1[rt]+=dp2[tt];
dp2[rt]+=dp1[tt];
}
}
int main()
{
//ios::sync_with_stdio(false);
int u,v;
scanf("%d",&n);
init();
for(int i=1;i<n;++i){
scanf("%d %d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,-1);
cout<<ans<<endl;
return 0;
}