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

聪聪可可

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

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

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

n &lt; = 20000 n&lt;=20000 n<=20000.


<mstyle mathcolor="blue"> </mstyle> \color{blue}{最初想法}

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


<mstyle mathcolor="red"> </mstyle> \color{red}{正解部分}

1 : 方法 1: 1: 树形dp

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

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

  • 更新答案: A n s <mtext>   </mtext> + = 2 F [ i , j ] F [ t o , 3 j ] Ans\ +=2* F[i, j]*F[to,3-j] Ans +=2F[i,j]F[to,3j]
  • 更新 d p dp dp数组: F [ i , j ] <mtext>   </mtext> + = F [ t o , ( ( j e d g e [ p ] . w ) % 3 + 3 ) % 3 ) ] F[i, j]\ += F[to, ((j-edge[p].w)\%3+3)\%3)] F[i,j] +=F[to,((jedge[p].w)%3+3)%3)] .

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


2 : 方法 2: 2:

. 待填坑. .


<mstyle mathcolor="red"> </mstyle> \color{red}{实现部分}

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

全部评论

相关推荐

点赞 评论 收藏
分享
在看牛客的社畜很积极:身高体重那一行信息去掉,学校那一行的信息放上面,找半天都没找到你是哪个学校什么专业的
点赞 评论 收藏
分享
rndguy:个人思路,抛砖引玉。 要我的话我先问清楚需求:要什么精度,什么速度,什么环境。 如果精度要求很低,平台也有点柔性的话,只需要输出pwm,然后开个中断记录各多少个脉冲,如果脉冲时间不对齐了就反馈控制电流加减就行。要求同步要求稍微高点的话可以在脉冲间做个线性插值,同步精度会高些。 但总体来说,如果直流有刷只有脉冲没有好的编码器的话很难做精准定位什么的(除非用一些电机磁路结构相关的奇技淫巧如高频注入什么的),所以要求更高就需要大量参数辨识和校准,那就慢多了。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务