[BZOJbegin][NOIP十连测第五场]Walk(数学相关+树形dp)

题目描述

[BZOJbegin][NOIP十连测第五场]Walk(数学相关+树形dp)_第1张图片

题解

设ans[i]表示长度为i的链gcd最大是多少,由于长度减小时gcd的最大值是不降的,显然ans[i]=max(ans[i],ans[i+1]);
那么考虑怎么求出ans[i]。
从小到大枚举gcd,将所有权值是gcd倍数的边都加到树,然后树形dp求最长链。
这样的话,每一个数的因数都是 n 级别的,所以每一条边都只会被加根n次,时间复杂度 O(n)

代码

#include
#include
#include
using namespace std;
#define N 1000005

int n,w,top,id,now,Max;
int ans[N],stack[N],vis[N],f[N],g[N];
struct hp{int x,y;}edge[N];
int tot,point[N],nxt[N*2],v[N*2];
int totw,pointw[N],nxtw[N*2],vw[N*2];

void _addedge(int x,int y)
{
    ++totw; nxtw[totw]=pointw[x]; pointw[x]=totw; vw[totw]=y;
}
void addedge(int i)
{
    ++tot; nxt[tot]=point[edge[i].x]; point[edge[i].x]=tot; v[tot]=edge[i].y;
    ++tot; nxt[tot]=point[edge[i].y]; point[edge[i].y]=tot; v[tot]=edge[i].x;
    stack[++top]=edge[i].x,stack[++top]=edge[i].y;
}
void dfs(int x,int fa)
{
    vis[x]=id;f[x]=g[x]=0;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            dfs(v[i],x);
            if (f[v[i]]+1>f[x])
            {
                g[x]=f[x];
                f[x]=f[v[i]]+1;
            }
            else g[x]=max(g[x],f[v[i]]+1);
        }
    now=max(now,f[x]+g[x]);
}
int main()
{
    freopen("walk.in","r",stdin);
    freopen("walk.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;iscanf("%d%d%d",&edge[i].x,&edge[i].y,&w);
        _addedge(w,i);Max=max(Max,w);
    }
    for (int i=1;i<=Max;++i)
    {
        top=0;++id;now=0;tot=0;
        for (int j=i;j<=Max;j+=i)
            for (int k=pointw[j];k;k=nxtw[k])
                addedge(vw[k]);
        for (int k=1;k<=top;++k)
            if (vis[stack[k]]!=id) dfs(stack[k],1);
        for (int k=1;k<=top;++k)
            point[stack[k]]=0;
        ans[now]=i;
    }
    for (int i=n;i>=1;--i)
        ans[i]=max(ans[i],ans[i+1]);
    for (int i=1;i<=n;++i) printf("%d\n",ans[i]);
}

你可能感兴趣的:(题解,dp,数学相关)