Solution
题目讲的比较明白,目的明确。就是每条边边权为1,求长为偶数的路径数。
我们知道 奇数+奇数=偶数;奇数+偶数=奇数;偶数+偶数=偶数;与异或运算比较相似。
我们用 记录以i为根节点的0代表偶数边数,1代表奇数边数。
那么我们知道,叶子节点的偶数路径有一条0,奇数路径没有。
所以我们可以推出状态转移方程
因为存在一条连向父节点的边,所以子节点只可以选择奇偶性不同的路径数相加。
得到上面递推式,我们在考虑更新答案,对于每个待处理父节点,已得到的偶数边*未处理过的子节点奇数边,或者奇偶互换,最终求和即得到最终偶数边个数。
时间复杂度 O(N)
Code:
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 1e5 + 7;
vector<int> e[N];
ll dp[N][2]; //0代表以i为根,偶数长度的边数,1代表奇数长度的边数
int n;
ll ans;
void dfs(int x, int fa) {
dp[x][0] = 1; //初始化长度0为偶数的边有一条
for (auto it : e[x]) {
if (it == fa) continue;
dfs(it, x);
ans += dp[x][0] * dp[it][1]; //以父节点被计算过的偶数边*未计算过的子节点奇数边(考虑连接父节点一条边)和为偶数,更新ans
ans += dp[x][1] * dp[it][0]; //以父节点被计算过的奇数边*未计算过的子节点偶数边(考虑连接父节点一条边)和为偶数,更新ans
dp[x][0] += dp[it][1]; //子节点奇数边+连接父节点边=偶数边合计父节点中
dp[x][1] += dp[it][0]; //子节点偶数边+连接父节点边=奇数边合计父节点中
}
}
int main() {
n = read();
for (int i = 1; i < n; ++i) {
int u = read(), v = read();
e[u].push_back(v); //建树
e[v].push_back(u);
}
dfs(1, 0);
printf("%lld\n", ans);
return 0;
}
京公网安备 11010502036488号