牛客最长树链

题目链接 最长树链

给出一颗树,每个结点都有一个权值,求出最长的一条链满足该链上所有点的最大公因数大于1。

和队友讨论了一下,队友给出了暴力建图跑树的直径思路,我感觉会tle,不过经过好几轮hack和反hack,慢慢摸索出了一种正解。

因为所要找的链的最大公因数要大于1,也就是这条链上所有结点都要有一个相同的质因子。

按照结点权值的质因子对所有结点进行分类,分类后按照质因子进行建图,对每个新建的图跑一次树的直径就是一条最长链。

例如样例,总共有 2 、 3 、 5 2、3、5 235三个质因子,第一次考虑质因子 2 2 2的时候,我们选择有质因子 2 2 2的点进行建树。剩下结点 1 、 2 、 4 1、 2、 4 124,对这三个点都跑一次树的直径(因为这个图不一定是一颗完整的树,可能是多个不连通的树。当然,我们也不必所有点跑一遍,这样必然会tle,我们可以对跑过的连通块进行标记)每次取max。

上面提到的选点的操作,事实上,我们可以将原来的树用一个vis数组标记,只有被标记的点才是我们选择的点。

t i p s : tips: tips:对于用于标记的数组,如果初始化操作要做很多次,我们可以用 v i s [ i ] = c t vis[i]=ct vis[i]=ct 标记 ,用 v i s [ i ] = = c t vis[i]==ct vis[i]==ct 判断,每一轮 c t + + ct++ ct++,这样会快很多

AC代码:

int a[maxn], vis[maxn], vis2[maxn];
int cnt = 1, head[maxn], dis[maxn], ct = 1, visct = 1;
struct node
{
    int u, v, nex;
} edge[maxn << 1];
void add(int u, int v)
{
    edge[cnt].v = v, edge[cnt].nex = head[u];
    head[u] = cnt++;
}
map<int, vector<int>> mp;
int p1, p2;
void bfs1(int u)
{
    visct++;
    dis[u] = 1;
    vis[u] = visct;
    queue<int> q;
    q.push(u);
    while (!q.empty())
    {
        u = q.front();
        q.pop();
        vis[u] = visct;
        for (int i = head[u]; i; i = edge[i].nex)
        {
            int v = edge[i].v;
            if (vis[v] != visct && vis2[v] == ct)
            {
                dis[v] = dis[u] + 1;
                p1 = v, vis[v] = visct, q.push(v);
            }
        }
    }
}
void bfs2(int u)
{
    visct++;
    dis[u] = 1;
    vis[u] = visct;
    queue<int> q;
    q.push(u);
    while (!q.empty())
    {
        u = q.front();
        q.pop();
        vis[u] = visct;
        for (int i = head[u]; i; i = edge[i].nex)
        {
            int v = edge[i].v;
            if (vis[v] != visct && vis2[v] == ct)
            {
                dis[v] = dis[u] + 1;
                p2 = v, vis[v] = visct, q.push(v);
            }
        }
    }
}
int main()
{
    int n, u, v, x;
    scanf("%d", &n);
    for (int i = 1; i < n; i++)
        scanf("%d %d", &u, &v), add(u, v), add(v, u);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &x);
        for (int j = 2; j * j <= x; j++) //按照质因子对点进行分类
        {
            bool fla = true;
            while (x % j == 0)
            {
                x /= j;
                if (fla)
                {
                    fla = false;
                    mp[j].push_back(i);
                }
            }
        }
        if (x > 1)
            mp[x].push_back(i);
    }
    int ans = 0;
    for (map<int, vector<int>>::iterator pt = mp.begin(); pt != mp.end(); pt++)
    {
        int nowct = visct; //记录这次质因子搜索的标记起始值
        int w = pt->first; //质因子w
        int len = mp[w].size();
        for (int i = 0; i < len; i++)
            vis2[mp[w][i]] = ct; //将含有质因子w的点记为实点
        for (int i = 0; i < len; i++)
        {
            if (vis[mp[w][i]] >= nowct && vis[mp[w][i]] <= visct) //避免同一块区域的反复搜索
                continue;
            bfs1(mp[w][i]);
            bfs2(p1); //bfs求树的直径
            ans = max(ans, dis[p2]);
        }
        ct++;
    }
    printf("%d\n", ans);
}
牛客练习赛142是一场编程竞赛,通常包含多个算法题目,涵盖如数组、字符串、表、动态规划等常见数据结构与算法知识点。针对这类比赛的解题思路和方法,可以从以下几个方面进行分析: ### 题目类型与解题策略 1. **数组相关问题** - 常见的题目包括查找数组中出现次数超过一半的数字、寻找缺失的数字、求解最子数组和等。 - 解题方法包括使用哈希表统计频率、摩尔投票法(适用于多数元素问题)、双指针技巧或前缀和优化。 2. **表操作** - 表题目可能涉及反转表、判断表是否有环、找出两个表的相交节点等。 - 例如,在找两个表相交点的问题中,可以先计算各自长度,然后让长表先走差值步数,再同步遍历比较节点地址[^3]。 3. **字符串处理** - 包括最长回文子串、无重复字符的最长子串等。 - 可采用滑动窗口、动态规划或中心扩展法等策略。 4. **与图** - 相关的题目可能涉及二叉的遍历、路径和、最近公共祖先等问题。 - 图论问题可能需要使用深度优先搜索(DFS)、广度优先搜索(BFS)或拓扑排序等算法。 5. **动态规划** - 动态规划常用于解决背包问题、最长递增子序列、编辑距离等。 - 关键在于定义状态转移方程,并通过迭代或记忆化搜索进行求解。 6. **贪心算法** - 适用于区间调度、活动选择、硬币找零等问题。 - 贪心策略的核心在于每一步都做出局部最优选择。 ### 示例代码:摩尔投票法解决“多数元素”问题 ```python def majorityElement(nums): count = 0 candidate = None for num in nums: if count == 0: candidate = num count += (1 if num == candidate else -1) return candidate ``` 该算法时间复杂度为 O(n),空间复杂度为 O(1),非常适合处理规模输入的数据集[^2]。 ### 提升解题能力的建议 - **刷题积累经验**:在 LeetCode、Codeforces、AtCoder 等平台上持续练习,熟悉各种题型。 - **学习经典算法**:掌握常见的算法模板,如二分查找、归并排序、快速选择等。 - **阅读官方题解与讨论区**:了解不同解法的优劣,尤其是最优解的时间复杂度分析。 - **模拟比赛训练**:定期参加在线编程比赛,提升实战能力和代码调试速度。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hesorchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值