ZCMU2016: 不存在的树 (2017浙江中医药校赛) (树链剖分)

21 篇文章 0 订阅
6 篇文章 0 订阅

点击打开链接


解析:

树链剖分的模板题,再套上一个线段树模板就可以了。

这里需要注意的是每找到一条链的过程中,这条链的点的在线段树中的编号是连续的。

就是第一条重链有n个点,编号就是1,...n

第二条重链有n-k个点,编号就是n+1...2n-k

以此类推,这样按照这些编号插入到线段树就可以了。

这些处理做完之后,就可以一一查询了

还有就是这道题我用vector存边就TLE了,用数组存就过了,不知道是我哪写错了还是这两种方式的问题


树链剖分大神讲解:点击打开链接

另一个讲了树面剖分的思想以及复杂度 点击打开链接 点击打开链接

以下内容属于摘录:

其实树链剖分就是把边哈希到线段树上的数据结构。

性质:从根到某一点的路径上轻边、重边的个数都不大于logn。

所以这样查找的时间复杂度相当于log2(n)

树链剖分可以解决很多问题,辅助一些线段树之类的数据结构可以解决一些树上修改的问题。还可以求LCA,不过复杂度比RMQ实现的LCA多一个log。


证明如下:

(1)

由于任一轻儿子对应的子树大小要小于父节点所对应子树大小的一半

因此从一个轻儿子沿轻边向上走到父节点后 所对应的子树大小至少变为两倍以上

经过的轻边条数自然是不超过log2Nlog2N

然后由于重链都是间断的 ((连续的可以合成一条))

所以经过的重链的条数是不超过轻边条数+1+1

因此经过重链的条数也是loglog级别的

综合可知原命题得证(这个证明zz表示看不懂)


(2)点击打开链接

每当学习了一个新算法,我们最关切的就是它的时间复杂度了。
如果树链剖分的时间复杂度高到爆,我们写的时候估计得虚死。
但是可以证明,树链剖分后的树,从根节点到任意一个叶节点的路径只会与O(logn)O(logn)条树链相交。这意味着将两个节点逼近到同一条链上时,只需经过O(logn)O(logn)次跳转。
即基于树剖的其它操作的时间复杂度为Ω(logn)Ω(logn)(注意是下界,具体的上界取决于操作本身的附加的复杂度)。

证明思路因该是这样的:
可以考虑一棵树中轻边的数量。由于从某一个节点开始,每走一条轻边,子树的大小都会减小一倍。因此任意一条树链上只有O(logn)O(logn)条轻边,即意味着只有O(logn)O(logn)



#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int MAXN = 50000 + 100;
#define INF 1147483647
#define M(a) memset(a,0,sizeof(a))
int val[MAXN],fa[MAXN],dep[MAXN],siz[MAXN],id[MAXN],top[MAXN],ran[MAXN],son[MAXN];

typedef struct ee
{
    int u,v;
    int next;
}ee;

ee edge[MAXN*2];
int head[MAXN],cnt;
int num,n;

typedef struct node
{
    int ii;
    int maxx;
    int sum;
}node;

node Btree[MAXN*4];

void addedge(int u,int v)
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

void dfs1(int x,int f,int d)
{
    dep[x]=d;
    siz[x]=1;
    son[x]=0;
    fa[x]=f;
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int tmp=edge[i].v;
        if(tmp==f) continue;
        dfs1(tmp,x,d+1);
        siz[x]+=siz[tmp];
        if(siz[son[x]]<siz[tmp])
        {
            son[x]=tmp;
        }
    }
}

void dfs2(int x,int tp)
{
    top[x]=tp;
    id[x]=++num;
    ran[num]=x;
    if(son[x]) dfs2(son[x],tp);
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int tmp=edge[i].v;
        if(tmp==fa[x]||tmp==son[x]) continue;
        dfs2(tmp,tmp);
    }

}

void build(int stu[],int l,int r,int root)
{
    if(l>r) return;
    if(l==r)
    {
        Btree[root].ii=l;
        Btree[root].maxx=stu[ran[l]];
        Btree[root].sum=stu[ran[l]];
        return;
    }

    int mid=(l+r)/2;
    build(stu,l,mid,root*2);
    build(stu,mid+1,r,root*2+1);

    Btree[root].maxx=max(Btree[root*2].maxx,Btree[root*2+1].maxx);
    Btree[root].sum=Btree[root*2].sum+Btree[root*2+1].sum;
}

void update(int root,int l,int r,int index,int val)
{
    if(l>r) return;
    if(l==r)
    {
        if(l==index)
        {
            Btree[root].maxx=Btree[root].sum=val;
        }
        return;

    }

    int mid=(l+r)/2;
    if(index<=mid)
        update(root*2,l,mid,index,val);
    else
        update(root*2+1,mid+1,r,index,val);

    Btree[root].maxx=max(Btree[2*root].maxx,Btree[2*root+1].maxx);
    Btree[root].sum=Btree[2*root].sum+Btree[2*root+1].sum;
}

int querymax(int root,int l,int r,int s,int e)
{
    if(r<s||l>e)
    {
        return -INF;
    }
    if(l>r) return -INF;
    if(s<=l&&r<=e)
    {
        return Btree[root].maxx;
    }
    int mid=(l+r)/2;
    return max(querymax(2*root,l,mid,s,e),querymax(root*2+1,mid+1,r,s,e));
}

int querysum(int root,int l,int r,int s,int e)
{
    if(r<s||l>e)
    {
        return 0;
    }
    if(l>r) return 0;
    if(s<=l&&r<=e)
    {
        return Btree[root].sum;
    }
    int mid=(l+r)/2;
    return (querysum(2*root,l,mid,s,e)+querysum(root*2+1,mid+1,r,s,e));
}


void solvemax(int a,int b)
{
    if(dep[top[a]]<dep[top[b]])
    {
        swap(a,b);
    }
    int ans=-INF;
    while(top[a]!=top[b])
    {
        //int tmp=top[a];
        ans=max(ans,querymax(1,1,n,id[top[a]],id[a]));
        a=fa[top[a]];
        if(dep[top[a]]<dep[top[b]])
        {
            swap(a,b);
        }
    }
    if(id[a]>id[b]) swap(a,b);
    ans=max(ans,querymax(1,1,n,id[a],id[b]));
    printf("%d\n",ans);

}


void solvesum(int a,int b)
{
    if(dep[top[a]]<dep[top[b]])
    {
        swap(a,b);
    }
    int ans=0;
    while(top[a]!=top[b])
    {
        //int tmp=top[a];
        ans=ans+querysum(1,1,n,id[top[a]],id[a]);
        a=fa[top[a]];
        if(dep[top[a]]<dep[top[b]])
        {
            swap(a,b);
        }
    }
    if(id[a]>id[b]) swap(a,b);
    ans+=querysum(1,1,n,id[a],id[b]);
    printf("%d\n",ans);
}


int main()
{
    int q;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        num=0;
        M(Btree);
        M(son);
        cnt=0;
        memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&val[i]);
        }
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        dfs1(1,0,1);
        dfs2(1,1);
        int mode,a,b;
        build(val,1,n,1);
        for(int i=0;i<q;i++)
        {
            scanf("%d%d%d",&mode,&a,&b);
            if(mode==0)
            {
                solvemax(a,b);
            }
            else if(mode==1)
            {
                solvesum(a,b);
            }
            else
            {
                update(1,1,n,id[a],b);
            }
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: zcmu 1093 简单计算器是一道编程题目,要求实现一个简单的计算器,能够进行加、减、乘、除四种基本运算。该题目主要考察编程基础能力和算法思维能力,需要熟练掌握基本的运算符和控制语句,能够设计合理的算法实现计算器功能。 ### 回答2: zcmu 1093 简单计算器是一种基于计算机技术的工具,用于进行基本算术运算,如加减乘除等。它能够简化我们在日常生活中的计算工作,提高计算效率,减少出错率。 使用zcmu 1093 简单计算器非常简单,只需输入需要计算的数字和符号,就能够得到计算结果。它可以进行多个数字之间的复杂运算,同时还支持小数、百分数、平方根等复杂运算。另外,zcmu 1093 简单计算器还可以存储中间计算结果,方便我们进行多步计算或调整计算过程。 除了日常的计算工作,zcmu 1093 简单计算器还可用于科学计算、工程设计等领域。许多专业软件都是基于简单计算器原理设计的,它们具有更高的计算精度和更复杂的运算能力,能够支持更高级别的科学计算和技术分析。 总之,zcmu 1093 简单计算器在日常生活中有着广泛的应用,它使我们的计算工作变得更加高效、准确。并且,随着科技的不断发展,这种计算工具也在不断地更新和改进,为我们的计算工作提供更加便捷、多样化的选择。 ### 回答3: ZCMU 1093 简单计算器是一道基础的算法题目,需要实现一个简单的计算器程序,支持加、减、乘、除四种基本运算,可以对两个整数进行运算并输出结果。 要实现这道题目,首先需要根据输入的运算符来判断应该进行的运算类型,并根据运算符的不同,执行不同的计算操作。同时,应注意除数不能为零的情况,避免程序出现异常。 在编写程序的过程中,可以使用 switch case 语句来判断不同的运算类型,并执行相应的计算操作。同时,为了能有效地判断输入的运算符,可以使用输入字符串的方式进行处理,提取出运算符进行比较。 此外,在程序中还需要进行合法性判断,确保输入的数字均为整数且在合理的范围内,以避免程序运行出现异常情况。同时,还需要考虑输入格式的问题,应确保输入的数字和运算符符合题目要求。 综上所述,ZCMU 1093 简单计算器是一道基础的算法题目,需要实现一个简单的计算器程序,支持加、减、乘、除四种基本运算,注意程序的合法性判断和输入格式的处理,能够熟练地运用 switch case 等语句完成程序的编写。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值