JZOJ3501. 【NOIP2013模拟联考15】消息传递

44 篇文章 0 订阅
32 篇文章 0 订阅

辣鸡题解

题目描述

给出一棵树,可以任意选择一个点作为起始点(消耗1时间)。在1单位时间内,每个上轮已访问过的点可以扩展一个节点,求最少花费时间和最少花费的起点。

20%

DP 。设 F[i] 表示传完 i 为根的子树所需要的最小时间。
F[i]=max(F[j]+j) json[i]F[j]F[j]

解释一下。
如果i有一些儿子要传,那么肯定先传该从儿子传下去时间大的,否则以后再传所需时间更长。
然后每个点都做一遍就可以20分了。
(25分?玄学)

话说我考试时要是交上去就有25分了

80% & 100%

官方的题解是说把一个点从作为根的父亲上移过去,使其成为新的根。
中途似乎要用到RMQ+二分之类的奇♂妙算法所以果断弃疗

咳咳
讲讲个人的奇♂妙算法。
20%的算法瓶颈在于要把每个点都算一遍,所以时间复杂度是 O(N2logn)

G[i] 表示假设以i为根的情况下,传遍i的父亲所在子树所需要的最小时间
似乎不易理解实际比题解好理解多了
这里写图片描述

至于 G[i] 的转移也很显然
这里写图片描述

每次从i的兄弟的F,还有i父亲的G中转移,具体类似F[i]的转移方式。

这样随便水一下能有80分


100分类似80分,只不过有些人工数据十分鬼畜,基本上是一个点连着剩下一片点,这样效率会退化到 O(n2logn)

然而解决方法也很简单,只需要从父亲直接推到儿子就行了。
因为一个父亲的儿子中只有一条边会改变,所以把转移的信息记录下来排序,根据每个儿子分别计算答案。
把一个边删掉后,在这条边之前(从大到小排序)的值不变,之后的会-1
问为什么的去看F的转移方程。

这里就不细说了因为太水

code

#include <iostream>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

int n,i,j,k,l,m2;
int a[200001][3];
int ls[200001];
int fa[200001];
int f[200001];
int g[200001];
int b[200002][2];
int m[200002];
int ans[200001];

int max(int x,int y) {return (x>y?x:y);}
int min(int x,int y) {return (x<y?x:y);}

void qsort(int l,int r)
{
    int i,j,k,mid;

    i=l;
    j=r;
    mid=b[(l+r)/2][0];

    while (i<=j)
    {
        while (b[i][0]>mid) i++;
        while (b[j][0]<mid) j--;

        if (i<=j)
        {
            k=b[i][0];
            b[i][0]=b[j][0];
            b[j][0]=k;
            k=b[i][1];
            b[i][1]=b[j][1];
            b[j][1]=k;

            i++;
            j--;
        }
    }

    if (l<j)
    qsort(l,j);

    if (i<r)
    qsort(i,r);

    return;
}

void dfs(int t)
{
    int i,j;

    f[t]=0;
    j=0;
    for (i=ls[t]; i; i=a[i][2])
    dfs(a[i][1]);

    for (i=ls[t]; i; i=a[i][2])
    b[++j][0]=f[a[i][1]];

    qsort(1,j);

    fo(i,1,j)
    f[t]=max(f[t],b[i][0]+i);

    return;
}

void dfs2(int t)
{
    int i,j;

    m2=0;
    j=0;
    for (i=ls[t]; i; i=a[i][2])
    b[++j][0]=f[a[i][1]],b[j][1]=a[i][1];
    if (fa[t])
    b[++j][0]=g[t],b[j][1]=0;

    qsort(1,j);

    fd(i,j,1)
    m[i]=max(m[i+1],b[i][0]+i);

    fo(i,1,j)
    {
        if (b[i][1])
        g[b[i][1]]=max(m2,m[i+1]-1);

        m2=max(m2,b[i][0]+i);
    }

    fo(i,1,j)
    m[i]=0;

    for (i=ls[t]; i; i=a[i][2])
    if (ls[a[i][1]])
    dfs2(a[i][1]);

    return;
}

int main()
{
    freopen("news.in","r",stdin);
    freopen("news.out","w",stdout);

    scanf("%d",&n);
    fo(i,1,n-1)
    {
        scanf("%d",&fa[i+1]);

        a[i][0]=fa[i+1];
        a[i][1]=i+1;
        a[i][2]=ls[a[i][0]];
        ls[a[i][0]]=i;
    }

    dfs(1);

    g[1]=0;
    dfs2(1);

    j=23333333;
    fo(i,1,n)
    {
        k=0;

        if (fa[i])
        b[++k][0]=g[i];

        for (l=ls[i]; l; l=a[l][2])
        b[++k][0]=f[a[l][1]];

        qsort(1,k);

        fo(l,1,k)
        ans[i]=max(ans[i],b[l][0]+l);
        ans[i]++;

        j=min(j,ans[i]);
    }

    printf("%d\n",j);
    fo(i,1,n)
    if (ans[i]==j)
    printf("%d ",i);
    printf("\n");

    fclose(stdin);
    fclose(stdout);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值