「题解」P6287 [COCI2016-2017#1] Mag

· · 题解

结论

为方便表述,称一条路径的节点数为其长度。

此题有这样的结论:

证明

下文中的所有内容均在正整数范围内讨论。

记路径 p 的长度为 l(p),魔力值为 f(p),所有节点魔力值的乘积为 m(p)

实现

实际上,我们可以统计所有仅有一个节点的魔力值为 2、其余节点魔力值均为 1 的路径,不影响答案。因为若魔力值为 2 的节点不在该路径正中间,则一定存在一条所有节点魔力值均为 1 的子路径,魔力值不小于原路径。

若子路径的魔力值和原路径相等,便可能出现需要约分的情况。我们可以优化统计答案的顺序,使得子路径的魔力值一定先于原路径被统计。

参考代码:

//注:本人实现代码时参考了 @programme_C 的题解。
#include<cstdio>
const int MAXN=1000000+10;
struct Node{
    int next;
    int to;
}edge[MAXN<<1];
int head[MAXN],cnt;
void add(int u,int v){
    edge[cnt].next=head[u];
    edge[cnt].to=v;
    head[u]=cnt++;
    return;
}
int mag[MAXN];
int f[MAXN],g[MAXN];
int p=1e9,q=1,mn=1e9;
void update(int x,int y){
    if(p*y>q*x){
        p=x,q=y;
    }
    return;
}
//更新答案。化除为乘。
void DP(int cur,int fa){
    int u1=0,u2=0,v1=0,v2=0;
    /*
        u1 表示 cur 的子结点中 f 值最大的节点;
        u2 表示 cur 的子结点中 f 值次大的节点;
        v1 表示 cur 的子结点中 g 值最大的节点;
        v2 表示 cur 的子结点中 g 值次大的节点;
    */
    for(int i=head[cur];~i;i=edge[i].next){
        int t=edge[i].to;
        if(t!=fa){
            DP(t,cur);
            if(f[t]>f[u1]){
                u2=u1,u1=t;
            }
            else if(f[t]>f[u2]){
                u2=t;
            }
            if(g[t]>g[v1]){
                v2=v1,v1=t;
            }
            else if(g[t]>g[v2]){
                v2=t;
            }
        }
        //更新 u1,u2,v1,v2。
    }
    if(mag[cur]==1){
        f[cur]=f[u1]+1;
        update(1,f[u1]+f[u2]+1);
        //注意顺序。避免出现需要约分的情况。
        if(v1!=0){
            g[cur]=g[v1]+1;
            if(u1!=v1){
                update(2,f[u1]+g[v1]+1);
            }
            //特判 cur 的子结点中 f 值最大的节点与 g 值最大的节点相同的情况。
            else{
                update(2,f[u2]+g[v1]+1);
                if(v2!=0){
                    update(2,f[u1]+g[v2]+1);
                }
            }
        }
        //特判 g 值为 0 的情况。此时无法构成符合要求的路径。
        //需要注意,f 值为 0 时仍能构成符合要求的路径。
    }
    else if(mag[cur]==2){
        g[cur]=f[u1]+1;
        update(2,f[u1]+f[u2]+1);
    }
    return;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        head[i]=-1;
    }
    for(int i=1;i<n;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;++i){
        scanf("%d",mag+i);
        if(mag[i]<mn){
            mn=mag[i];
        }
    }
    if(mn>1){
        printf("%d/1",mn);
        return 0;
    }
    //特判无魔力值为 1 的节点的情况。
    DP(1,0);
    printf("%d/%d",p,q);
    return 0;
}