1. 程式人生 > 其它 >COCI2015/2016 Contest#4 D

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導致死迴圈,所以特判掉。