1. 程式人生 > >副本1——dp-URAL

副本1——dp-URAL

看的別人的思路,很清晰;點選開啟連結

題目大意: 求給定區間[X,Y]中滿足下列條件的整數個數:這個數恰好等於K 個互不相等的 B 的整 數次冪之和。例如,設 X=15,Y=20,K=2,B=2,則有且僅有下列三個數滿足題意: 17 = 24+20, 18 = 24+21, 20 = 24+22。 輸入:第一行包含兩個整數X 和 Y。接下來兩行包含整數 K 和 B。 輸出:只包含一個整數,表示滿足條件的數的個數。 資料規模:1 ≤ X ≤ Y ≤ 231−1,1 ≤ K ≤ 20, 2 ≤ B ≤ 10。 分析: 所求的數為互不相等的冪之和,亦即其B 進製表示的各位數字都只能是 0和 1。因此, 我們只需討論二進位制的情況,其他進位制都可以轉化為二進位制求解。 很顯然,資料範圍較大,不可能採用列舉法,演算法複雜度必須是 log(n)級別,因此我們 要從數位上下手。
 淺談數位類問題 劉聰
第 2頁,共 12 頁
本題區間滿足區間減法,因此可以進一步簡化問題:令 count[i..j]表示[i..j]區間內合法數 的個數,則count[i..j]=count[0..j]-count[0..i-1]。換句話說,給定 n,我們只需求出從0 到 n 有多少個符合條件的數。 假設 n=13,其二進位制表示為 1101,K=3。我們的目標是求出 0 到 13 中二進位制表示含 3 個 1 的數的個數。為了方便思考,讓我們畫出一棵高度為 4 的完全二叉樹:


為了方便起見,樹的根用 0 表示。這樣,這棵高度為4 的完全二叉樹就可以表示所有 4 位二進位制數(0..24-1) ,每一個葉子節點代表一個數。其中,紅色路徑表示 n。所有小於 n的 陣列成了三棵子樹,分別用藍色、綠色、紫色表示。因此,統計小於 13 的數,就只需統計 這三棵完整的完全二叉樹:統計藍子樹內含 3 個 1 的數的個數、統計綠子樹內含 2 個1 的數 的個數(因為從根到此處的路徑上已經有 1 個 1),以及統計紫子樹內含 1個 1 的數的個數。 注意到,只要是高度相同的子樹統計結果一定相同。而需要統計的子樹都是“右轉”時遇到 的。當然,我們不能忘記統計n 本身。實際上,在演算法最初時將 n 自加 1,可以避免討論 n 本身,但是需要注意防止上溢。 剩下的問題就是,如何統計一棵高度為 i的完全二叉樹內二進位制表示中恰好含有 j 個1 的數的個數。這很容易用遞推求出:設 f[i,j]表示所求,則分別統計左右子樹內符合條件數的 個數,有 f[i,j]=f[i-1,j]+f[i-1,j-1]。 這樣,我們就得出了詢問的演算法:首先預處理 f,然後對於輸入 n,我們在假想的完全 二叉樹中,從根走到 n所在的葉子,每次向右轉時統計左子樹內數的個數。 下面是 C++程式碼:

void init()
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=31;i++)
    {
        f[i][0]=f[i-1][0];
        for(int j=1;j<=i;j++)
        {
             f[i][j]=f[i-1][j]+f[i-1][j-1];
        }
    }
}
int getans(int x,int k)
{
    int tot=0;//當前1數目
    int ans=0;
    for(int i=31;i>0;i--)
    {
        if(x&((long long)1<<i))
        {
             tot++;
             if(tot>k)//如果已經取到所有k的情況,則結束
                break;
             x=x^((long long)1<<i);
        }
        if((1<<(i-1))<=x)
            ans+=f[i-1][k-tot];
    }
    if(tot+x==k)  //因為i不取到0 所以對於x最低位為1或0的情況進行特判增加
          ans++;
    return ans;
}

最後的問題就是如何處理非二進位制。對於詢問 n,我們需要求出不超過 n 的最大 B 進位制 表示只含 0、1的數:找到n 的左起第一位非 0、1 的數位,將它變為 1,並將右面所有數位 設為 1。將得到的 B 進製表示視為二進位制進行詢問即可。 預處理遞推 f的時間複雜度為O(log2(n)),共有 O(log(n))次查詢,因此總時間複雜度為 O(log2(n))。 實際上,最終的程式碼並不涉及樹的操作,我們只是利用圖形的方式來方便思考。因此也 可以只從數位的角度考慮:對於詢問 n,我們找到一個等於 1 的數位,將它賦為 0,則它右 面的數位可以任意取,我們需要統計其中恰好含有 K-tot 個 1 的數的個數(其中 tot 表示這 一位左邊的1 的個數) ,則可以利用組合數公式求解。逐位列舉所有”1”進行統計即可。 我們發現,之前推出的 f 正是組合數。同樣是採用“逐位確定”的方法,兩種方法異曲 同工。當你覺得單純從數位的角度較難思考時,不妨畫出圖形以方便思考。

AC程式碼

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>


using namespace std;
const long long maxx=((long long )1<<31)-1;
int f[50][50];
int num[50];
void init()
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1;i<=31;i++)
    {
        f[i][0]=f[i-1][0];
        for(int j=1;j<=i;j++)
        {
             f[i][j]=f[i-1][j]+f[i-1][j-1];
        }

    }

}
int get(long long y,int b)
{
    int cnt=0;
    int nowy=0;
    while(y>0)
    {
        num[cnt++]=y%b;
        y/=b;
    }
    reverse(num,num+cnt);
    int chant=cnt;
    for(int i=0;i<cnt;i++)
    {
      //  cout<<num[i];
        if(num[i]>1)
        {
            chant=i;
            break;
        }
    }
   // cout<<endl;
    for(int i=0;i<cnt;i++)
    {
        if(i<chant)
          nowy=nowy*2+num[i];
        else
            nowy=nowy*2+1;
    }
  //  cout<<nowy<<endl;
    return nowy;
}
int getans(int x,int k)
{
    int tot=0;//當前1數目
    int ans=0;
    for(int i=31;i>0;i--)
    {
        if(x&((long long)1<<i))
        {
             tot++;
             if(tot>k)//如果已經取到所有k的情況,則結束
                break;
             x=x^((long long)1<<i);
        }
        if((1<<(i-1))<=x)
            ans+=f[i-1][k-tot];
    }
    if(tot+x==k)  //因為i不取到0 所以對於x最低位為1或0的情況進行特判增加
          ans++;
    return ans;
}


int main()
{
    init();
    long long x,y;
    long long k,b;
    cin>>x>>y;
    cin>>k>>b;
    int nowy=get(y,b);
    int nowx=get(x-1,b);
    int ans=getans(nowy,k)-getans(nowx,k);
    //cout<<getans(nowy,k)<<endl;
    //cout<<getans(nowx,k)<<endl;
    cout<<ans<<endl;


    return 0;
}