牛客最长树链

最长树链:质因子分治求最大公因数大于1的路径

题目链接 最长树链

给出一颗树,每个结点都有一个权值,求出最长的一条链满足该链上所有点的最大公因数大于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);
}
评论
成就一亿技术人!
拼手气红包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、付费专栏及课程。

余额充值