残阳止水

ACM算法

合约数


题目描述


给定一棵n个节点的树,并且根节点的编号为p,第i个节点有属性值vali, 定义F(i): 在以i为根的子树中,属性值是vali的合约数的节点个数。y 是 x 的合约数是指 y 是合数且 y 是 x 的约数。小埃想知道1000000007取模后的结果.

输入描述:

输入测试组数T,每组数据,输入n+1行整数,第一行为n和p,1<=n<=20000, 1<=p<=n,

接下来n-1行,每行两个整数u和v,表示u和v之间有一条边。第n+1行输入n个整数val1, val2,…, valn,

其中1<=vali<=10000,1<=i<=n.

输出描述:

对于每组数据,输出一行,包含1个整数, 表示对1000000007取模后的结果

示例1

输入

2
5 4
5 3
2 5
4 2
1 3
10 4 3 10 5
3 3
1 3
2 1
1 10 1

输出

11
2

备注:

n>=10000的有20组测试数据


思路

一次DFS即可:复杂度:O(n*sqrt(max(val)))

1. dfs到一个节点的时候,先计算一下已经dfs过的节点中,有多少个节点是这个节点的合约数。(刚遍历到这个点的时候,它的子节点肯定都还没遍历到。)

2. 在dfs回溯到这个节点的时候,再计算一下已经dfs过的节点中,有多少个节点是这个节点的合约数。(回溯到这个点的时候,它的子节点肯定已经都遍历完了。)

两者相减就得到了该点的F[i]。(两者减一下就是这个节点的子节点中是该节点合约数的个数。)


细节:如果一个节点的权值是合数,自身要且仅要计算一次。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAX = 20000+100;
const int MOD = 1000000007;

int n,p;
vector<int>mp[MAX];
ll val[MAX];
ll num[MAX];//num[i]表示权值为i的节点个数
ll ans;

bool vis[MAX*2];
ll prime[MAX];
int tot;

bool heshu[MAX]; //如果i为合数,则heshu[i]=true

//求质数
void getprime()
{
    memset(vis,false,sizeof(vis));
    tot=0;
    for(int i=2;i<=MAX;i++)
    {
        if(!vis[i]) prime[++tot]=i;
        for(int j=1;j<=tot&&i*prime[j]<=MAX;j++)
        {
            vis[i*prime[j]]=true;
            if(i%prime[j]==0) break;
        }
    }
}

void dfs(int root,int fa)
{
    ll valnow=val[root];
    num[valnow]++;
    if(heshu[valnow]==true)
        ans=(ans+root)%MOD;
    //如果是叶子节点的话就返回
    if(mp[root].size()==1&&fa!=-1)
        return;
    ll res1,res2;
    //先遍历到的时候先计算一次
    res1=0;
    for(int j=2;j<=sqrt(valnow);j++)
    {
        if(valnow%j==0)
        {
            if(heshu[j]==true)
            {
                res1+=num[j];
            }
            if(heshu[valnow/j]==true&&valnow/j!=j)
            {
                res1+=num[valnow/j];
            }
        }
    }
    if(heshu[valnow]==true)
    {
        res1+=num[valnow];
    }
    for(int i=0;i<mp[root].size();i++)
    {
        if(mp[root][i]==fa)
            continue;
        dfs(mp[root][i],root);
    }
    //回溯到的时候再计算一次
    res2=0;
    for(int j=2;j<=sqrt(valnow);j++)
    {
        if(valnow%j==0)
        {
            if(heshu[j]==true)
            {
                res2+=num[j];
            }
            if(heshu[valnow/j]==true&&valnow/j!=j)
            {
                res2+=num[valnow/j];
            }
        }
    }
    //只有当前节点的权值是合数的时候,才可能有合约数
    if(heshu[valnow]==true)
    {
        res2+=num[valnow];
        ans=(ans+1LL*root*(res2-res1))%MOD;
    }

}

int main()
{
    int T;
    scanf("%d",&T);
    
    //预处理合数
    getprime();
    int cnt=3;
    memset(heshu,false,sizeof(false));
    for(int i=4;i<=10010;i++)
    {
        if(i<prime[cnt])
        {
            heshu[i]=true;
        }
        else if(i>=prime[cnt])
            cnt++;
    }
    
    while(T--)
    {
        scanf("%d%d",&n,&p);
        memset(num,0,sizeof(num));
        for(int i=0;i<=n;i++)
            mp[i].clear();
        for(int i=0;i<n-1;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            mp[u].push_back(v);
            mp[v].push_back(u);
        }
        for(int i=1;i<=n;i++)
            scanf("%lld",&val[i]);
        ans=0;
        if(n==1)
        {
            if(heshu[val[1]]==true)
                ans=(ans+1)%MOD;
            printf("%lld\n",ans);
            continue;
        }
        dfs(p,-1);
        printf("%lld\n",ans);
    }
    return 0;
}


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luyehao1/article/details/79974725
文章标签:
个人分类: 思维题 ICPC铜牌题
上一篇Mergeable Stack
下一篇求逆元
想对作者说点什么? 我来说一句

算法分析与设计最大约数问题

2011年05月16日 2KB 下载

没有更多推荐了,返回首页

关闭
关闭