COCI2015/2016 Contest#4 D

COCI2015/2016 Contest#4 D

题意:

​ 给出一个有 \(n\) 个节点的 \(k\) 叉树. \(k\) 叉树是指每个点最多有 \(k\) 个儿子的树(每一条边权值为1).每个节点的编号就是该节点加入树的顺序,每个节点从左往右添加到树中,节点 \(x\) 的深度增加当且仅当在它左边的每个节点都已经被填满了.(节点从1开始编号.)你现在要做的就是输出 \(Q\) 个询问的答案.每个询问 \((x,y)\) ,表示从 \(x\)\(y\) 的最小步数。\((1\le N \le 10^{15})\)


这道题不难,就是我被hack了。。。。

​ 所以在开篇补充一下,根据文中做法做的话在处理 \(k=1\) 的情况时会出现问题,所以当 \(k=1\) 时直接特判掉,此时 \((x,y)=\max(x,y)-\min(x,y)\)

​ 我们看到这道题,首先联想到的肯定是 LCA。 但是注意到此题的数据范围,正常的倍增 LCA 是肯定过不了的。但是我们发现,根据题目的条件,他给的树一共只会有 \(\log(n)\) 层,那么,我们就可以省略倍增,直接模拟 倍增LCA 的过程,但是我们一次就爬一层,这样我们的时间复杂度就是 \(O(Q\times \log(N))\)

​ 那么,我们目前的任务就是要在 \(O(1)\) 的时间内求出一个节点的父亲节点。同样的,我们还得根据一个节点的编号求出它所在的层数。

​ 首先我们来求层数,因为我们只需要求一次层数,后面在跳的时候可以手动把层数更新,这样避免重复调用求层数的函数。注意到,在题目所给的树中,第一层有 \(k^0\) 个节点,第二层有 \(k^1\) 个节点,我们可以发现规律,第 \(x\) 层就有 \(k^{x-1}\) 个节点,那么前 \(a\) 层就会有:\(\sum_{i-1}^{n} k^{i-1}\) 个节点。所以我们可以枚举层数,来进行判断,这样的时间复杂度是 \(O(\log(n))\)

​ 那么对于父亲节点我们又怎么求呢?假设现在有一个点的编号为 \(x\),而且他的层数为 \(d\)。那么我们先让求出 \(x-\sum_{i=1}^{d-1} k^{i-1}\) ,这个就是 \(x\) 在该层的编号,设这个值为 \(v\)。为了让每 \(k\) 个值除以 \(k\) 得到的商一致,我们将 \(v\)减去1。又因为每一个 \(d-1\) 层的节点在这一层都有 \(k\) 个儿子节点。我们算出 \((v-1)/k\) 这即是它父亲节点在上一层从左往右数的个数-1。综上所述,我们可以得到对于节点 \(x\) 他的父亲节点的编号为:

\[((x-\sum\limits_{i=1}^{d-1}{k^{i-1}}-1)/k)+1+\sum\limits_{i=1}^{d-2}{k^{i-1}} \]

​ 对于 \(\sum_{i=1}^{n}{k^{i-1}}\) 我们可以用数组预处理出来,到时候直接调用。

​ 那么接下来直接照着 LCA 的步骤去模拟就好了。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,k,q,up;
ll num[105];
ll solve_dep(ll x)
{
    ll p=1;
    ll dep=0;
    while(x>p)
    {
        x-=p;
        p*=k;
        ++dep;
    }
    return dep+1;
}
 
ll fa(ll x,ll dep)
{
    ll cnt=0,p=1,d;
    cnt=num[dep-1];
    d=x-cnt-1;
    d=d/k+1;
    cnt=num[dep-2];
    return cnt+d;
}
int main()
{
    scanf("%lld %lld %lld",&n,&k,&q);
    ll tmp=1;
    for(int i=1;i<=100;++i)
        num[i]=num[i-1]+tmp,tmp*=k;
    ll x,y,dep[2],ans=0;
    while(q--)
    {
        ans=0;
        scanf("%lld %lld",&x,&y);
        if(k==1)
        {
        	printf("%lld\n",max(x,y)-min(x,y));
        	continue;
		}
        if(x<y) swap(x,y);
        dep[0]=solve_dep(x),dep[1]=solve_dep(y);
        while(dep[0]>dep[1]) x=fa(x,dep[0]),dep[0]--,ans++;
        while(x!=y)
        {
            x=fa(x,dep[0]),y=fa(y,dep[1]);
            dep[0]--,dep[1]--;
            ans+=2;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

​ 文末再提一笔, \(k=1\) 的情况下在求父亲节点时会不断的除以1导致死循环,所以特判掉。

posted @ 2021-06-26 19:53  夜空之星  阅读(49)  评论(0编辑  收藏  举报