hdu 5468 Puzzled Elena

一颗大小为(n<=100000) 的树,根为1,每个节点有个权值。问:每个节点 和 以这个节点为根的子树中 有多少权值和根的权值互质。

做法,我们首先要明白一个东西,就是当你已经知道了k个数,并且求出来了每个数的素因子,如何很快的求出第k+1个数,有多少个和前k个数互质。

这个做法可以容斥做,也可以用莫比乌斯反演,莫比乌斯反演要更快些。

因为每个数的大小<=100000 这个范围内   2*3*5*7*11*13 *17> 100000 最多只有 6 个素因子。   

当我们知道这个怎么处理以后,我们可以利用dfs序,解决这个问题

我们求当前这个节点的答案时,用容斥搞就是:以这个节点为根的树的大小 - 有1个素因子和他相同的节点个数 + 有2个素因子和他相同的个数 - 有3个素因子和他相同的个数 ....

那么问题来了,我们如何求出有多少个 有1个素因子和他相同的个数,有2个素因子和他相同的个数 ,,,,,

我们维护一个数a[]数组,a[i] 代表包含因子为i 的节点个数。

那么在这颗树中,进入这颗树之前求一下(有1个素因子和他相同的节点个数,有2个素因子和他相同的节点个数.....),离开这颗树的时候再求一下(有1个素因子和他相同的节点个数,有2个素因子和他相同的节点个数.....),他们的差便是我们需要的(子树中的和他有关系的信息)。

到这里,问题就解决了,容斥版的做法 时间复杂度 O(n*2^6 + nlogn) :


#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 1000;
int prim[maxn],cnt;
bool vis[maxn];
vector<int>F[maxn];
void get_prim()
{
    memset(vis,0,sizeof(vis)); cnt = 0;
    for(int i=0;i<maxn;i++) F[i].clear();
    for(int i=2;i<maxn;i++)
    if(!vis[i]){
        prim[cnt++] = i;
        for(int j=i;j<maxn;j+=i){
            F[j].push_back(i);
            vis[j] = 1;
        }
    }
}
int a[maxn];
int ans[maxn],val[maxn];
vector<int>G[maxn];
vector<int>factor;
int fatcnt;
void getFactor(int value){
    factor = F[value];
    fatcnt = factor.size();
}
void add(int x)
{
    getFactor(val[x]);
    int all = (1ll<<fatcnt);
    for(int i=1;i<all;i++){
        int tmp = 1;
        for(int j=0;j<fatcnt;j++) if( i&(1ll<<j) ){
            tmp *= factor[j];
        }
        a[tmp] ++ ;
    }
}
int dfs_clock;
int st[maxn],ed[maxn];
void dfs(int x,int fa)
{
    int fuck[100];
    st[x] = ++dfs_clock;
    getFactor(val[x]);
    int all = (1ll<<fatcnt);
    for(int i=1;i<all;i++){
        int tmp = 1;
        for(int j=0;j<fatcnt;j++) if( i&(1ll<<j) ){
            tmp *= factor[j];
        }
        fuck[i] = a[tmp] ;
    }
    for(int i=0;i<G[x].size();i++){
        int v = G[x][i];
        if(v == fa) continue;
        dfs(v,x);
    }
    ed[x] = dfs_clock;
    getFactor(val[x]);
    all = (1ll<<fatcnt);
    ans[x] = ed[x] - st[x];
    for(int i=1;i<all;i++){
        int tmp = 1,cnt=0;
        for(int j=0;j<fatcnt;j++) if( i&(1ll<<j) ){
            tmp *= factor[j];cnt++;
        }
        if(cnt & 1){
            ans[x] -= (a[tmp] - fuck[i]);
        }
        else{
            ans[x] += (a[tmp] - fuck[i]);
        }
    }
    add(x);
    if(val[x] == 1) ans[x]++;
}
int main()
{
    get_prim();
    int n,cas=0;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++) G[i].clear();
        for(int i=1;i<n;i++){
            int x,y;scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
        }
        for(int i=1;i<=n;i++) scanf("%d",&val[i]);
        memset(a,0,sizeof(a));
        dfs_clock = 0;
        dfs(1,-1);
        printf("Case #%d:",++cas);
        for(int i=1;i<=n;i++){
            printf(" %d",ans[i]);
        }
        puts("");
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值