P2634 [国家集训队]聪聪可可 [点分治(待填坑) /树形dp]
聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。
他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。
聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。
.
点分治? 不会, 打暴力跳过…
树形dp
设 表示一个端点为 点, 含有多少边权和 的路径,
因为自己到自己为一条路径, 所以初值 , 利用子树进行转移, 具体地说:
从头到尾扫所有子树, 设扫到中间时, 已有的路径数为 , 当前子树的路径数为 ,
- 更新答案:
- 更新数组: .
两个端点都为自己的路径 在上述累加方式中没有涉及到, 所以最后答案要加上 .
点分治
- 注意最后答案要加上 .
- 注意统计答案时要乘 .
- 在处理模意义下的负数时不能够直接取绝对值 .
#include<bits/stdc++.h>
#define reg register
const int maxn = 20004;
int N;
int Ans;
int num0;
int head[maxn];
int F[maxn][3];
struct Edge{ int nxt, to, w; } edge[maxn<<1];
void Add(int from, int to, int w){
edge[++ num0] = (Edge){ head[from], to, w };
head[from] = num0;
}
int Mod(int x){ return (x%3+3) % 3; }
int Gcd(int a, int b){ return !b?a:Gcd(b, a%b); }
void DFS(int k, int fa){
F[k][0] = 1;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(to == fa) continue ;
DFS(to, k);
for(reg int j = 0; j < 3; j ++) Ans += 2*F[k][j]*F[to][Mod(-j-edge[i].w)];
for(reg int j = 0; j < 3; j ++) F[k][j] += F[to][Mod(j-edge[i].w)];
}
}
int main(){
scanf("%d", &N);
for(reg int i = 1; i < N; i ++){
int x, y, w;
scanf("%d%d%d", &x, &y, &w); w %= 3;
Add(x, y, w), Add(y, x, w);
}
DFS(1, 0);
Ans += N; //!
int tot = N*N;
int gcd = Gcd(tot, Ans);
Ans /= gcd, tot /= gcd;
printf("%d/%d\n", Ans, tot);
return 0;
}