Puzzled Elena HDU - 5468(dfs序+容斥定理)

Puzzled Elena HDU - 5468

 Since both Stefan and Damon fell in love with Elena, and it was really difficult for her to choose. Bonnie, her best friend, suggested her to throw a question to them, and she would choose the one who can solve it.

Suppose there is a tree with n vertices and n - 1 edges, and there is a value at each vertex. The root is vertex 1. Then for each vertex, could you tell me how many vertices of its subtree can be said to be co-prime with itself?
NOTES: Two vertices are said to be co-prime if their values' GCD (greatest common divisor) equals 1. 

Input
There are multiply tests (no more than 8).
For each test, the first line has a number n (1≤n≤105), after that has n−1 lines, each line has two numbers a and b (1≤a,b≤n), representing that vertex a is connect with vertex b. Then the next line has n numbers, the ith number indicates the value of the ith vertex. Values of vertices are not less than 1 and not more than 105
.
Output
For each test, at first, please output “Case #k: “, k is the number of test. Then, please output one line with n numbers (separated by spaces), representing the answer of each vertex.
Sample Input

5
1 2
1 3
2 4
2 5
6 2 3 4 5

Sample Output

Case #1: 1 1 0 0 0

题意:

给定一棵树,求这个节点的所有子树中包括他本身与它互质的节点的个数

分析:

因为数据分为是1e5,因此最多只有6个素因子 2 × 3 × 5 × 7 × 11 × 13 × 17 > 1 e 5

又因和互质有关因此想到容斥定理,具体如何实现呢

可以使用dfs序,实际上下面的做法并不是规范的dfs序,具体dfs序讲解可以看博客dfs序和欧拉序,因为我也是第一次做dfs序的题

实际上这道题也可说是利用dfs的性质

首先我们需要预处理出,每个数它的素因子是什么,这个直接在埃氏筛的过程中存储即可

我们需要维护一个fac[]数组,fac[i]代表含有因子i的节点个数,注意,fac[]数组并不是一开始就预先处理好的,而是一开始都是0,只有退出一个点的时候(即以后不会再进入这个点了),才更新fac数组,此时这个点容斥过程中构成的因子i,fac[i]+=1

当我们遍历到一个点的时候,如果是刚刚第一次进入这个点,此时利用容斥定理计算一下,得到一个数字L,当退出这个点的时候在利用容斥定理计算一下得到R,此时R-L是这个点的子树中,所以和这个点不互质的个数。

为什么作差?

假设我们遍历完了一个子树,现在遍历另一个子树的时候,到了根节点,这是是刚进入,我们计算一下,此时计算的个数,其实是其他已经遍历完的点中和当前节点不互质的个数,因为只有当遍历完退出的时候我们才更新fac数组,等要退出这个节点的时候,它的子树都遍历完了,都退出了,也就是fac数组也都更新完了,此时在计算一边就是包含子树的节点和之前遍历完的节点中和当前节点不互质的个数了,因此一作差就是当前节点子树中和和它不互质的个数。

在dfs中我们顺便求出子树的所有节点个数,这样用子树所有节点个数减去与根节点不互质的个数就是互质的个数,这个时候保存答案就行了,注意如果当前这个根节点是1,答案要+1,因为他和他本身就是互质的。

对于容斥定理的部分不再多说了,就是枚举每个数质因子的选取状态,然后一一枚举即可

code:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;

int fac[maxn];//fac[i]记录当前已经遍历过的(已经退出这个节点了)含有因子i的节点个数

int w[maxn];//每个节点的代表数字
int ans[maxn];

struct Graph{
    vector<int> vc[maxn];
    void init(){
        for(int i = 0; i < maxn; i++) vc[i].clear();
    }
    void add(int u,int v){
        vc[u].push_back(v);
    }
}G1,G2;

void prepare(){
    G1.init();
    for(int i = 2; i < maxn; i++){//其实就是埃氏筛
        if(G1.vc[i].size())
            continue;
        for(int j = i; j < maxn; j += i){//这里我们把每个数的素因子都存下来即vc[j]就表示j这个数中含有i这个素因子
            G1.add(j,i);
        }
    }
}

int calc(int u,int x){//这个函数就利用容斥定理计算已经遍历过的点(已经退出来了)和当前点互质的个数,即先求不互质的所有情况
    int ret = 0;
    int n = G1.vc[u].size();
    for(int i = 1; i < (1 << n); i++){//枚举每种组合状态
        int tot = 1,cnt = 0;//tot记录组合出的因子,cnt表示组合出的这个因子是由几个素因子组成的
        for(int j = 0; j < n; j++){
            if((1 << j) & i){
                cnt++;
                tot = tot * G1.vc[u][j];
            }
        }
        if(cnt & 1) ret = ret + fac[tot];//如果奇数就加上已经知道的遍历过的点含有这个因子的,即和当前点公因子为tot的个数
        else ret = ret - fac[tot];//偶数就减去
        fac[tot] += x;//如果x=1,说明退出这个点的时候,就更新fac[tot]即加上当前点,如果x=0,说明刚进入先就不加
    }
    return ret;
}

int dfs(int u,int pre){
    int cnt = 0;//记录子树的节点总个数
    int L = calc(w[u],0);//刚进入算一遍
    for(int i = 0; i < G2.vc[u].size(); i++){//遍历子树
        int v = G2.vc[u][i];
        if(v == pre) continue;
        cnt += dfs(v,u);//统计子树节点数
    }
    int R = calc(w[u],1);
    ans[u] = cnt - (R - L);//子树节点总个数减去子树中和根节点所有不互质的个数就是互质的个数了
    if(w[u] == 1) ans[u]++;//如果根节点是1,在加上1
    return cnt + 1;
}
int main(){
    prepare();
    int n,cas = 0;
    while(~scanf("%d",&n)){
        G2.init();
        memset(fac,0,sizeof(fac));
        int u,v;
        for(int i = 1; i < n; i++){
            scanf("%d%d",&u,&v);
            G2.add(u,v);
            G2.add(v,u);
        }
        for(int i = 1; i <= n; i++){
            scanf("%d",&w[i]);
        }
        dfs(1,0);
        printf("Case #%d:",++cas);
        for(int i = 1; i <= n; i++){
            printf(" %d",ans[i]);
        }
        puts("");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/codeswarrior/article/details/82708033