1. 程式人生 > 實用技巧 >分數樹

分數樹

19世紀的時候,Moriz Stern(1858)與Achille Brocot(1860)發明了“一棵樹”。據說,經由一些簡單的規則而產生的這一棵樹上,可以包含零以上所有的有理數。這棵樹看起來大致這樣: 此題 列 對應 我們傳統的行

你觀察出規則了嗎?
首先,它們在第一列放兩個“分數”,第一個是0/1,代表0;第二個是1/0,代表無窮大。接著它們一列一列的產生這棵樹,當它們要產生第k+1列的時候,就先把前k列所有的分數按照大小排成一列(假設有n個),在這些數之間會有n-1個間隔,那麼第k+1列就準備產生n-1個數,其值的分子恰好是左右兩個數的分子的和,分母是左右兩個數的分母的和。
例如,2/3,而它的2就是左邊1/2的1和右邊1/1的分子1相加的結果;而2/3的3,則是1/2的2加上1/1的分母1而得。 從這棵樹中,我們可以看出,每個正的最簡分數在這棵樹中恰好出現一次,我們用字母“L”和“R”分別表示從樹根(1/1)開始的一步“往左走”和“往右走”,則每一個數都可以由L和R組成的序列表示。
例如,LRRL表示從1/1開始往左走一步到1/2,然後往右走到2/3,再往右走到3/4,最後往左走到5/7。我們可以把LRRL看作5/7的一種表示法。幾乎每個正分數均有唯一的方法表示成一個由L和R組成的序列。
給定一個分數,輸出它的LR表示法。
【輸入】
輸入檔案fraction.in有兩個互為素數的正整數m和n(1<=n,m<=1000)。
【輸出】
輸出檔案fraction.out為對應的LR表示法。
【輸入】
5 7
【輸出】
LRRL

思路(參考了網上dalao的基本思路)

一個結點的左兒子一定小於它,右兒子一定大於它,所以建一個名為cc[ ]的結構體,儲存它左邊那位的分子,分母以及右邊那位的分子,分母以及自己的分子,分母;
將根結點初始化為1/1,並將它的左邊右邊以及它自己錄入,然後開始搜尋;
並且你又會發現,如果要尋找的那個比它小時,就會往左,接下來的那個結點就會繼承它的左邊,而那個結點的右邊就會更新為當前搜尋的結點,然後再繼續搜尋那個結點,一直到當前搜尋的結點的值與要尋找的分數相等時就退出搜尋。

程式碼

#include<bits/stdc++.h>
using namespace std;
double a,b,x,y;

struct fs{
    double left1,left2,right1,right2;
    double fm,fz;
}cc[1000050];

int find(int n){
    y=(cc[n].fz*1.0)/(cc[n].fm*1.0);
    if(x<y){
        cc[n+1].left1=cc[n].left1;
        cc[n+1].left2=cc[n].left2;
        cc[n+1].right1=cc[n].fz;
        cc[n+1].right2=cc[n].fm;
        cc[n+1].fz=cc[n].left1+cc[n].fz;
        cc[n+1].fm=cc[n].left2+cc[n].fm;
        cout<<"L";
        return find(n+1);
    } 
    if(x>y){
        cc[n+1].left1=cc[n].fz;
        cc[n+1].left2=cc[n].fm;
        cc[n+1].right1=cc[n].right1;
        cc[n+1].right2=cc[n].right2;
        cc[n+1].fz=cc[n+1].left1+cc[n+1].right1;
        cc[n+1].fm=cc[n+1].left2+cc[n+1].right2;
        cout<<"R";
        return find(n+1);
    }
    return 0;

}

int main(){
    freopen("fraction.in","r",stdin);
    freopen("fraction.out","w",stdout);
    cin>>a>>b;
    if(a==b)return 0;
    cc[0].left1=0;
    cc[0].left2=1;
    cc[0].right1=1;
    cc[0].right2=0;
    cc[0].fm=1;
    cc[0].fz=1;
    x=(a*1.0)/(b*1.0);
    find(0);

}