P2634 [国家集训队]聪聪可可 [点分治(待填坑) /树形dp]

聪聪可可

聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃、两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好了,可是他们已经玩儿腻了这种低智商的游戏。

他们的爸爸快被他们的争吵烦死了,所以他发明了一个新游戏:由爸爸在纸上画n个“点”,并用n-1条“边”把这n个“点”恰好连通(其实这就是一棵树)。并且每条“边”上都有一个数。接下来由聪聪和可可分别随即选一个点(当然他们选点时是看不到这棵树的),如果两个点之间所有边上数的和加起来恰好是3的倍数,则判聪聪赢,否则可可赢。

聪聪非常爱思考问题,在每次游戏后都会仔细研究这棵树,希望知道对于这张图自己的获胜概率是多少。现请你帮忙求出这个值以验证聪聪的答案是否正确。

n<=20000n<=20000.


color{blue}{最初想法}

点分治? 不会, 打暴力跳过…


color{red}{正解部分}

1:方法 1: 树形dp

F[i,j]F[i, j] 表示一个端点为 ii 点, 含有多少边权和 %3=j\%3=j的路径,
因为自己到自己为一条路径, 所以初值 F[i,0]=1F[i,0]=1, 利用子树进行转移, 具体地说:

从头到尾扫所有子树, 设扫到中间时, 已有的路径数为 F[i,j]F[i, j], 当前子树的路径数为 F[to,j]F[to, j],

  • 更新答案: Ans +=2F[i,j]F[to,3j]Ans +=2* F[i, j]*F[to,3-j]
  • 更新dpdp数组: F[i,j] +=F[to,((jedge[p].w)%3+3)%3)]F[i, j] += F[to, ((j-edge[p].w)\%3+3)\%3)] .

两个端点都为自己的路径 在上述累加方式中没有涉及到, 所以最后答案要加上 NN .


2:方法 2: 点分治

.待填坑.


color{red}{实现部分}

  • 注意最后答案要加上 NN .
  • 注意统计答案时要乘 22 .
  • 在处理模意义下的负数时不能够直接取绝对值 .
#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
", Ans, tot);
        return 0;
}

原文地址:https://www.cnblogs.com/zbr162/p/11822507.html