NOIP 專題 枚舉(霧)

數位統計問題

請統計某個給定範圍[L, R]的所有整數中,數字2出現的次數。

比如給定範圍[2, 22],數字2在數2中出現了1次,在數12中出現1次,在數20中出現1次,在數21中出現1次,在數22中出現2次,所以數字2在該範圍內一共出現了6次。

枚舉法:直接從L~R枚舉,對每一個x進行2出現次數的統計。

數學計數法:以每一個數位爲統計單元進行計數,利用前綴和的思想直接處理出1~n中滿足性質的數的個數f(n),則答案即爲f(r)-f(l-1)。下圖以第三高位作爲統計單元爲例:

 

代碼:

#include<iostream>
#include<vector>
using namespace std;

int f(int n){//1~n中2出現的次數
    vector<int>num;
    while(n){
        num.push_back(n%10);
        n/=10;
    }
    n=num.size();
    int i,j,left,right,power,ans=0;
    for(i=n-1;i>=0;--i){
        left=right=0;
        power=1;
        for(j=n-1;j>i;--j){
            left=left*10+num[j];
        }
        for(j=i-1;j>=0;--j){
            right=right*10+num[j];
            power*=10;
        }
        ans+=left*power;
        if(num[i]==2)ans+=right+1;
        else if(num[i]>2)ans+=power;
    }
    return ans;
}

int main(){
    int l,r;
    cin>>l>>r;
    cout<<f(r)-f(l-1);
    return 0;
}

加強版問題:

試計算在區間1 到n 的所有整數中,數字x(0 ≤ x ≤ 9)共出現了多少次?

例如,在1到11 中,即在1、2、3、4、5、6、7、8、9、10、11 中,數字1 出現了4 次。

顯而易見的是,如果x!=0,則直接爲上述考慮方案,唯一需要獨立考慮的爲x=0即前導0的情況。

需要注意的是,本身abcdef這個數字的表示即說明了a位置上不可能取0,所以在討論0出現的次數時,對於最高位,不應該進行討論,因爲如果最高位取0,會產生前導0,不應統計在內;如果不取0,也不可能進行第二種情況的討論,因爲若a=0,後面可取00000~99999的情況還是有前導0,所以對於x=0的情況,不需要討論最高位。

注:儘管可以將兩個特判結合,但爲了簡化思路,直接分開x是否爲0的兩種情況計算。

#include<iostream>
#include<vector>
using namespace std;

int f(int n,int x){//1~n中2出現的次數
    vector<int>num;
    while(n){
        num.push_back(n%10);
        n/=10;
    }
    n=num.size();
    int i,j,left,right,power,ans=0;
    if(!x){
        for(i=n-1;i>=0;--i){
            left=right=0;
            power=1;
            for(j=n-1;j>i;--j){
                left=left*10+num[j];
            }
            --left;
            for(j=i-1;j>=0;--j){
                right=right*10+num[j];
                power*=10;
            }
            ans+=left*power;
            if(num[i]==0)ans+=right+1;
            else if(num[i]>0)ans+=power;
        }
    }
    else{
        for(i=n-1;i>=0;--i){
            left=right=0;
            power=1;
            for(j=n-1;j>i;--j){
                left=left*10+num[j];
            }
            for(j=i-1;j>=0;--j){
                right=right*10+num[j];
                power*=10;
            }
            ans+=left*power;
            if(num[i]==x)ans+=right+1;
            else if(num[i]>x)ans+=power;
        }
    }
    return ans;
}

int main(){
    int n,x;
    cin>>n>>x;
    cout<<f(n,x);
    return 0;
}


雙指針優化循環

在社交媒體上,經常會看到針對某一個觀點同意與否的民意調查以及結果。例如,對某一觀點表示支持的有 1498 人,反對的有 902 人,那麼贊同與反對的比例可以簡單的記爲 1498:902 。

不過,如果把調查結果就以這種方式呈現出來,大多數人肯定不會滿意。因爲這個比例的數值太大,難以一眼看出它們的關係。對於上面這個例子,如果把比例記爲 5:3 ,雖然與真實結果有一定的誤差,但依然能夠較爲準確地反映調查結果,同時也顯得比較直觀。

現給出支持人數A,反對人數 B ,以及一個上限 L ,請你將 A 比 B 化簡爲 A ’比 B ’,要求在 A ’和 B ’均不大於 L 且 A ’和 B ’互質(兩個整數的最大公約數是 1 )的前提下, A ’ /B ’ ≥ A/B 且 A ’ /B ’ - A/B 的值儘可能小。

輸入描述:

輸入共一行,包含三個整數 A,B,L ,每兩個整數之間用一個空格隔開,分別表示支持人數、反對人數以及上限。

輸出描述:

輸出共一行,包含兩個整數 A ’, B ’,中間用一個空格隔開,表示化簡後的比例。

備註:

對於 100% 的數據, 1 ≤ A ≤ 1,000,000,1 ≤ B ≤ 1,000,000,1 ≤ L ≤ 100,A/B ≤ L

枚舉法:由於L的數據範圍很小,可以直接枚舉A,B,選擇符合要求的答案即可。

關於互質要求的判斷:此題沒有必要判斷互質,因爲若x和y互質,最大公因子爲d,x和y均<L,那麼上下同時除去d的結果爲x'=x/d,y'=y/d,其中x'與y'也是滿足<L的條件,因此x'/y'會在x/y之前計算,也就是互質的會比不互質的先算,並且不互質的不會導致答案改變即本次計算並不會導致差值變小,只要我們在設計算法的時候要求差值嚴格小於之前的結果時更新結果即可。

關於差值的增減性判斷:

1.如果爲了避免浮點計算的誤差,將式子進行交叉相乘得,A'B-B'A>0,即當A'B-B'A的結果大於0且小於上次的結果時再更新。

#include<iostream>
using namespace std;
int main(){
    int A,B,L,i,j,a,b,delta=1e9;
    cin>>A>>B>>L;
    for(i=1;i<=L;++i){
        for(j=1;j<=L;++j){
            if(i*B-A*j>=0&&i*B-A*j<delta){
                delta=i*B-A*j;
                a=i,b=j;
            }
        }
    }
    cout<<a<<" "<<b;
    return 0;
}

2.由於本題的數據範圍小,對於double來說精度誤差可以有效控制,因此可以直接用double來比較增量。

#include<iostream>
using namespace std;
int main(){
    int A,B,L,i,j,a,b;
    cin>>A>>B>>L;
    double delta=1e9,X=(double)A/B,x;
    for(i=1;i<=L;++i){
        for(j=1;j<=L;++j){
            x=(double)i/j;
            if(x>=X&&x-X<delta){
                delta=x-X;
                a=i,b=j;
            }
        }
    }
    cout<<a<<" "<<b;
    return 0;
}

雙指針優化:假設我們現在有一個初始的A’/B‘,現在要求能夠使得A'/B'-A/B取得最小的A'與B',爲了保證A'/B'>=A/B,因此我們勢必要增大分子,而爲了是兩者距離更近,同時我們也需要增大分母,分母的增加必須保證A'/B'>=A/B,因此就A'和B'各自的變化趨勢來看都是單調不減的,因而可以採用雙指針算法將O(n^2)做法優化至O(n)。

可能的疑惑點:讓分子變小一點分母增大一點並且保持結果>=A/B的情況是否可能發生?

疑惑解答:首先我們規定對每一個分母j,求一個分子i是使得i/j剛好滿足i/j>=A/B的第一個i,那麼上述疑問可以轉換成,存在j'>j,i'<i,且i'/j'>=A/B。其實這種情況和我們的規定是相違背的。

我們已知,i/j>=A/B,i'/j'>=A/B,j'>j,i'<i那麼i'/j>i'/j'>=A/B,那麼當j作爲分子的時候,求取的第一個滿足條件的分子應該是i'而不是i,與規定矛盾。

代碼中爲了實現方便,尤其是避免分母爲0的情況,因此優先對分母進行枚舉。

#include<iostream>
using namespace std;
int main(){
    int A,B,L,i,j,a,b,delta=1e9;
    cin>>A>>B>>L;
    for(i=0,j=1;j<=L;++j){
        while(i<=L&&i*B-A*j<0)++i;
        if(i<=L&&i*B-A*j<delta){//如果i處於滿足<=L的範圍並且可以進行更新
            a=i,b=j;
            delta=i*B-A*j;
        }
    }
    cout<<a<<" "<<b;
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章