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個素因子
又因和互質有關因此想到容斥定理,具體如何實現呢
可以使用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;
}