模擬題——數17 的簡單解法
數17 Problem Description 小cos是寒樹中學一名機敏睿智的男生。最近他偶然地發現同班的小cot——極為神祕的女生——正想方設法地收集他的資訊。小cos非常不解。這天他收到了來自小cot的一封郵件,要他數出從a到b(包括a和b)的正整數中共有多少個“17”。(如817517有兩個“17”) 小cot打著什麼算盤?“17”蘊含著什麼不為人知的祕密?……不,你不需要管這些……你的任務是回答小cot的問題。 小cos是寒樹中學一名機敏睿智的男生。最近他偶然地發現同班的小cot——極為神祕的女生——正想方設法地收集他的資訊。小cos非常不解。這天他收到了來自小cot的一封郵件,要他數出從a到b(包括a和b)的正整數中共有多少個"17"。(如817517有兩個"17") 小cot打著什麼算盤?"17"蘊含著什麼不為人知的祕密?……不,你不需要管這些……你的任務是回答小cot的問題。 Input 第1行兩個正整數a和b,意義見問題描述。 Output 輸出一個整數,表示a到b的正整數中"17"的數量。 Sample Input 1 200 Sample Output 12 Data Constraint 對於30%的資料,有a≤b≤1,000 對於50%的資料,有a≤b≤100,000 對於100%的資料,有a≤b,a,b均在 int 範圍內。
首先,看到題目,我們很容易知道,它詢問的是,對於一個區間 [a,b],裡面所有的數中,有多少個"17",例如 117 有1個"17", 1717171 有6個"17"。 那麼,我們很容易做出一個簡單的暴力程式,計算出一個數有多少個"17"。 程式碼如下。
#include <cstdio> #define LL long long using namespace std; LL a,b,ans; LL find(LL a) { LL p=0; while (a) { if (a%100==17) p++,a/=100; else a/=10; } return p; } int main() { scanf("%lld%lld",&a,&b); for (LL k=a;k<=b;k++) ans+=find(k); printf("%lld",ans); return 0; }
這個程式可以幫助我們做幾件事。
- 直接作為程式碼提交可以拿到部分分。
- 可以用計算機暴力經過一段時間,以此求出所有情況,然後用分塊的方法暴力來大大降低複雜度。例如,如果我們將17的數量以105分塊,那麼,我們的複雜度最大就是 2*105+104,也就是所有分塊的求和,以及左右兩端這兩塊的單獨求和。那麼,這個複雜度就相當的低了。因為int最大值為2147483647(231-1)
- 其次,這可以作為我們正確演算法的一個驗算的根據。在我們演算法的正確性不能得到保障的情況下,經過多次大資料的驗算是一個非常有效的驗證演算法正確性的方法。
所以,我們就開始思考正確方法。 我們知道,如果我們要判斷一個數x有沒有17,那麼判斷的次數應該是β(β=x的位數)。而對於a~b的數都要進行判斷,很顯然,這種方法是極其不優的。 那麼,我們可以逆向思維。我們不選擇計算一個一個數含有"17",而是反過來,我們列舉每兩個相鄰的位上的"17",並計算出在這兩個位是"17"的情況下,有多少個數滿足條件。而這樣,我們就可以運用組合數學的方法進行進一步處理。 當然,補充一下,我們確定兩位是"17"後,計算出[a,b]總共有多少個數滿足這兩位是"17",也可以擴充套件一下,就是[1,b]有多少個數滿足這兩位是"17",並用它減去[1,a-1]有多少個數滿足這兩位是"17"。 因此,我們不斷列舉兩個位是"17",並且計算出此時滿足在[1,b]、[1,a-1]上的數的值並且不斷做差。 那麼,我們就可以通過這樣的值進行求和。 不過,我們需要明確的是,對於一個數1717在[a,b]範圍內,我們列舉百位、前位是"17"時,就會考慮到這個數1717,而在我們列舉個位、十位是"17"時,也會考慮到這個數1717,那麼,它就被考慮了兩次,正好與其1717有兩個"17"相應,所以,我們可以很確定的知道,我們只需要列舉"17"的位置,而無需在意考慮的數是否還有別的"17",因為其如果還有別的"17",必然會在另外的列舉中被考慮到。 所以,我們可以寫出如下程式碼。
#include<cstdio>
#define LL long long
using namespace std;
LL c[14];
LL ans,a;
LL find(LL );
int main()
{
c[0]=c[1]=1;
for (LL k=2;k<=13;k++)
c[k]=c[k-1]*10;
scanf("%lld",&a);
a--;
ans=find(a);
scanf("%lld",&a);
printf("%lld",find(a)-ans);
return 0;
}
LL find(LL x)
{
if (x<17)
return 0;
LL m=13,out=0;
while (c[m]>x)
m--;
m++;
LL y,now;
for (LL k=m;k>=3;k--)
{
y=x%c[k];
now=y/c[k-2];
out+=(x/c[k])*c[k-2];
if (now>17)
out+=c[k-2];
else
if (now==17)
out+=(k==3?1:((y%c[k-2])+1));
}
return out;
}