HDU 6430 TeaTree(预处理+dfs /线段树)

该博客介绍了一道HDU 6430的算法题,要求计算树上节点对的最大公约数(GCD)中的最大值。博主分析了问题的规模,指出两两枚举的方法会超时,提出从每个节点出发预处理其因子,并在DFS过程中合并子节点的因子集合来找到最大GCD。博主尝试了两种合并策略:使用vector暴力合并和权值线段树合并,并分享了两种方法的运行时间。
摘要由CSDN通过智能技术生成

题目链接:我是链接!!

题意:给一颗以1为根节点的树,每个节点有一个权值,计算每个节点以当前节点为最近公共祖先的节点对gcd中的最大值。

分析:数据量是100000,如果两两枚举计算的话不管怎么样是肯定会T的,所以从每个数考虑。预处理出每个数的所有因子,最大数为100000,每个数的因子数不会大于400个。根据每个节点的权值,可以得到每个节点的因数集合。然后进行dfs,将每个节点的因数集合与它的儿子的因数集合合并,合并的过程中就可以计算因数集合中最大的相同的数,也就是我们要求的gcd的最大值。

因数集合的保存和合并有两种方式,第一种是用vector存因数,每次直接利用类似于归并排序中合并的方法进行暴力合并。另一种是用权值线段树,每次合并就合并两颗树。第一种大概跑了1000ms,第二种方法跑了2600ms。(数据结构什么的不存在的)

代码:

vector暴力合并

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 100000 + 5;
const int mx = 100000;
int n;
vector<int>g[N];
vector<int>di[N];
vector<int>hav[N];
int ans[N];
void init()
{
    for(int i = 1; i <= mx; i++)
    {
        for(int j = i; j <= mx; j += i)
        {
            di[j].push_back(i);
        }
    }
}
void merge(int x,int v)
{
    int i = 0, j = 0;
    vector<int>tem;
    for(; i < hav[x].size() && j < hav[v].size();)
    {
        int a1 = hav[x][i], a2 = hav[v][j];
        if(a1 < a2)
        {
            tem.push_back(a1);
            i++;
        }
        else if(a1 == a2)
        {
            ans[x] = max(ans[x],a1);
            tem.push_back(a1); 
            i++;j++; 
        }
        else
        {
            tem.push_back(a2); 
            j++;
        }
    }
    for(; i < hav[x].size(); i++)tem.push_back(hav[x][i]);
    for(; j < hav[v].size(); j++)tem.push_back(hav[v][j]);
    hav[x] = tem;
    return;
}
void dfs(int x)
{
    ans[x] = -1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int v = g[x][i];
        dfs(v);
        merge(x,v);
    }
    return;
}
int main()
{
     init();
     scanf("%d",&n);
     for(int i = 2; i <= n; i++)
     {
         int a; scanf("%d",&a);
         g[a].push_back(i);
     }
     for(int i = 1; i <= n; i++)
     {
         int val; scanf("%d",&val);
         hav[i] = di[val];
     }
     dfs(1);
     for(int i = 1; i <= n; i++) printf("%d\n",ans[i]);
     return 0;
}

权值线段树合并

#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 100000 + 5;
const int mx = 100000;
int n;
vector<int>g[N];
vector<int>di[N];
int ans[N];
void init()
{
    for(int i = 1; i <= mx; i++)
    {
        for(int j = i; j <= mx; j += i)
        {
            di[j].push_back(i);
        }
    }
}
int root[N], tot = 0;
int lson[N*400],rson[N*400],maxx[N*400];
void pushup(int d)
{
    if(lson[d] && rson[d]) maxx[d] = max(maxx[lson[d]],maxx[rson[d]]);
    else if(lson[d]) maxx[d] = maxx[lson[d]];
    else if(rson[d]) maxx[d] = maxx[rson[d]];
    return;
}
void update(int &d,int l,int r,int p)
{
    if(d == 0) d = ++tot;
    if(l == r)
    {
        maxx[d] = p;
        return ;
    }
    int m = (l+r) >> 1;
    if(p <= m) update(lson[d],l,m,p);
    else update(rson[d],m+1,r,p);
    pushup(d);
    return;
}
int merge(int rt,int srt,int x)
{
    if(rt == 0 || srt == 0) return rt^srt;
    if(maxx[rt] == maxx[srt]) ans[x] = max(ans[x],maxx[rt]);
    if(lson[rt] || lson[srt]) lson[rt] = merge(lson[rt],lson[srt],x);
    if(rson[rt] || rson[srt]) rson[rt] = merge(rson[rt],rson[srt],x);
    pushup(rt);
    return rt;
}
void dfs(int x)
{
    ans[x] = -1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int v = g[x][i];
        dfs(v);
        root[x] = merge(root[x],root[v],x);
    }
    return;
}
int main()
{
     init();
     tot = 0;
     scanf("%d",&n);
     for(int i = 2; i <= n; i++)
     {
         int a; scanf("%d",&a);
         g[a].push_back(i);
     }
     for(int i = 1; i <= n; i++)
     {
         int val; scanf("%d",&val);
         root[i] = 0;
         for(int j = 0; j < di[val].size();j++)
         {
             update(root[i],1,mx,di[val][j]);
         }
     }
     dfs(1);
     for(int i = 1; i <= n; i++) printf("%d\n",ans[i]);
     return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值