Codeforces 842C Ilya And The Tree

本文探讨了Codeforces842C Ilya And The Tree问题的解决方案,介绍了一种使用树形DP的方法来求解每个节点到根节点路径上的最大GCD。通过详细解释算法思想及其实现细节,包括区间GCD的快速收敛特性、状态转移过程中的去重优化等,为读者提供了清晰的解题思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Codeforces 842C Ilya And The Tree

树上gcd

题目

有一棵根节点为1的树,每个结点都有权值。求每个结点从根节点到它的路径上的点的最大gcd(最多可以把一个路径上的一个点权值变成0)。

思路

关于区间gcd。。有个结论:就是可以‘暴力’,因为区间gcd收敛非常快,无论是在线段树搞,还是这次这题。都是暴力。线段树处理区间gcd的一题

开始写树形dp,dp开两个记录当前节点的路径最大gcd,dp[u][0]表示不删节点,dp[u][1]表示删除当前节点。果断WA。因为比如一条路是5,6,10,扫到6时,最大gcd是去掉5,得6,但是扫到10时,只能从6转移过来,认为区间gcd是1。说到底是dp记录的信息不够,不能获得父亲所有可能的gcd的情况。

因为区间gcd收敛很快,所以我们开n个vector,记录每个节点可能的gcd情况,暴力的转移。为了优化时间,每个节点的gcd情况要排序并去重。

%%%

由于一个数的因子个数为松散的 n ,而且一条路径上的gcd会很快的将到1。所以如果我们从根节点往下dfs,记录所有可以通过把1个或0个点权值变成0得到的gcd。对于每个点,答案就是转移到当前点的所有gcd的最大值。
时间复杂度为O(V∗d),其中V为顶点数,d为因子数。

代码

VS2015跑会在去重那块RE,提示越界。不知道为什么,gcc是能过的。
这里写图片描述

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
#define M(a,b) memset(a,b,sizeof(a))
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=200007;
typedef long long LL;
struct Edge
{
    int to, ne;
}e[MAXN<<1];
int head[MAXN], v[MAXN], edgenum;
inline void addedge(int u, int v)
{
    edgenum++;e[edgenum].to=v, e[edgenum].ne=head[u], head[u]=edgenum;
    edgenum++;e[edgenum].to=u, e[edgenum].ne=head[v], head[v]=edgenum;
}
vector<int> dp[MAXN];
int fgcd[MAXN];
int gcd(int a, int b) { return b==0 ? a : gcd(b, a%b); }
void dfs(int u, int fa)
{
    if(fa==-1)
    {
        fgcd[u]=v[u];
        dp[u].push_back(0);
        dp[u].push_back(v[u]);
    }
    else
    {
        fgcd[u]=gcd(fgcd[fa], v[u]);
        dp[u].push_back(fgcd[fa]);
        for(int i=0;i<dp[fa].size();i++)
            dp[u].push_back(gcd(dp[fa][i], v[u]));
        sort(dp[u].begin(), dp[u].end());
        dp[u].erase(unique(dp[u].begin(), dp[u].end()), dp[u].end());
    }
    for(int i=head[u]; ~i;i=e[i].ne)
        if(e[i].to!=fa)
            dfs(e[i].to, u);
}
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
            scanf("%d", &v[i]);
        M(head, -1);edgenum=0;M(dp, 0);
        for(int i=1;i<n;i++)
        {
            int u, v;cin>>u>>v;
            addedge(u, v);
        }
        for(int i=1;i<=n;i++) dp[i].clear();
        dfs(1, -1);
        for(int i=1;i<=n;i++)
            printf("%d%c", dp[i][dp[i].size()-1], i==n ? '\n' : ' ');
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值