Walk

【问题描述】
给定一棵n个节点的树,每条边的长度为 1,同时有一个权值
w。定义一条路径的权值为路径上所有边的权值的最大公约数。现在
对于任意i∈[1,n],求树上所有长度为 i的简单路径中权值最大的
是多少。如果不存在长度为 i的路径,则第 i行输出 0。
【输入格式】
第一行,一个整数 n,表示树的大小。
接下来n-1行,每行三个整数u,v,w,表示u,v间存在一条权值
为w的边。
【输出格式】
对于每种长度,输出一行,表示答案。
【样例输入】
3
1 2 3
1 3 9
【样例输出】
9
3
0
【数据范围及约定】
对于30%的数据,n≤1000。
对于额外30%的数据,w≤100。
对于100%的数据,n≤4*10^5,1≤u,v≤n,w≤10^6。

分析:
树上的操作,
嗯很好,我们要在边上做文章

考虑一下gcd的性质,
我们不需要什么厉害的结论
gcd(a,b)<=min(a,b) 显然吧

考虑边的取值不大,可以枚举一下gcd
假设枚举的gcd是i
那么什么样的路径可以使答案=i哩
当然是权值>i的边啦
废话。。。光是权值大没有用啊,ta还要有i这个因子啊
换句话说就是权值要是i的倍数
这样我们就可以在枚举gcd的时候,
加入相应的边,找到一条树上的最长路径,更新答案,

嗯,好像已经很完美了
等等,我们怎么在这么多边中找到一个特定权值的所有变呢
难道要O(n)遍历吗
显然不行,
这里就有一个小技巧了:
平常我们在加边的时候,大概都是这么写:

void add(int u,int w,int z)
{
    tot++;
    way[tot].x=u;
    way[tot].y=w;
    way[tot].v=z;
    way[tot].nxt=st[u];
    st[u]=tot;
}

st数组是以每一个起点为端点的所有边的起点值
那么我们能不能以权值为关键字呢
当然可以:有想法就有未来!!!
那么存边的时候就变成了这样:

void Add(int u,int w,int z)
{  //用权值当做关键字 
    tt++;
    way[tt].x=u;
    way[tt].y=w;
    way[tt].nxt=sta[z];
    sta[z]=tt;
}
这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=400005;
struct node{
    int x,y,nxt;
};
node e[N],way[N<<1];
int st[N],sta[1000010],tt=0,tot=0;
int p[N<<1],num=0;  //判重
int ans[N],mxlen,mx,n,link[N<<1]; 

void Add(int u,int w,int z)  //用权值当做关键字
{
    tt++;
    e[tt].x=u;e[tt].y=w;e[tt].nxt=sta[z];sta[z]=tt;
    //单项加边即可 
}

void add(int u,int w)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;link[tot]=u;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;link[tot]=w;
    //link的意思好像就是每条边的起点,不知道ta存在的意义是什么 
}

int dfs(int now)  //找树上最长路径 
{
    p[now]=num;  //visit;
    int mxson=0;
    for (int i=st[now];i;i=way[i].nxt)
        if (p[way[i].y]!=num)  //not visit
        {
            int r=dfs(way[i].y);
            mxlen=max(mxlen,r+mxson+1);
            mxson=max(mxson,r+1);
        }
    return mxson;
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        mx=max(mx,z); //找到最大权值  
        Add(u,w,z);
    }
    for (int i=1;i<=mx;i++) //mx是gcd上界 
    {   //i是我们枚举的gcd 
        for (int j=i;j<=mx;j+=i)
            for (int k=sta[j];k;k=e[k].nxt)   //相同权值的加入树中
                add(e[k].x,e[k].y);
        mxlen=0;
        num++;
        for (int k=1;k<=tot;k++)
            if (p[link[k]]!=num) dfs(link[k]);
        for (int k=1;k<=tot;k++) st[link[k]]=0;
        //memset(st,0,sizeof(st));
        tot=0;
        ans[mxlen]=i;  //枚举的gcd 
    }
    for (int i=n-1;i>0;i--) ans[i]=max(ans[i],ans[i+1]);  //ans一定是不增的 
    for (int i=1;i<=n;i++) printf("%d
",ans[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673466.html