天天看點

2017年第八屆藍橋杯C/C++組省賽真題解析1. 購物單2.等差素數列3.承壓計算4.方格分割5.取數位 6.最大公共子串7.日期問題8.包子湊數9.分巧克力 10.k倍區間

目錄

1. 購物單

2.等差素數列

3.承壓計算

4.方格分割(待)

5.取數位

6.最大公共子串

7.日期問題(待)

8.包子湊數

9.分巧克力

10.k倍區間

1. 購物單

    小明剛剛找到工作,老闆人很好,隻是老闆夫人很愛購物。老闆忙的時候經常讓小明幫忙到商場代為購物。小明很厭煩,但又不好推辭。

    這不,XX大促銷又來了!老闆夫人開出了長長的購物單,都是有打折優惠的。

    小明也有個怪癖,不到萬不得已,從不刷卡,直接現金搞定。

    現在小明很心煩,請你幫他計算一下,需要從取款機上取多少現金,才能搞定這次購物。

    取款機隻能提供100元面額的紙币。小明想盡可能少取些現金,夠用就行了。

    你的任務是計算出,小明最少需要取多少現金。

以下是讓人頭疼的購物單,為了保護隐私,物品名稱被隐藏了。

-----------------

****     180.90       88折

****      10.25       65折

****      56.14        9折

****     104.65        9折

****     100.30       88折

****     297.15        半價

****      26.75       65折

****     130.62        半價

****     240.28       58折

****     270.62        8折

****     115.87       88折

****     247.34       95折

****      73.21        9折

****     101.00        半價

****      79.54        半價

****     278.44        7折

****     199.26        半價

****      12.97        9折

****     166.30       78折

****     125.50       58折

****      84.98        9折

****     113.35       68折

****     166.57        半價

****      42.56        9折

****      81.90       95折

****     131.78        8折

****     255.89       78折

****     109.17        9折

****     146.69       68折

****     139.33       65折

****     141.16       78折

****     154.74        8折

****      59.42        8折

****      85.44       68折

****     293.70       88折

****     261.79       65折

****      11.30       88折

****     268.27       58折

****     128.29       88折

****     251.03        8折

****     208.39       75折

****     128.88       75折

****      62.06        9折

****     225.87       75折

****      12.89       75折

****      34.28       75折

****      62.16       58折

****     129.12        半價

****     218.37        半價

****     289.69        8折

--------------------

需要說明的是,88折指的是按标價的88%計算,而8折是按80%計算,餘者類推。

特别地,半價是按50%計算。

請送出小明要從取款機上提取的金額,機關是元。

答案是一個整數,類似4300的樣子,結尾必然是00,不要填寫任何多餘的内容。

特别提醒:不許攜帶電腦入場,也不能打開手機。

1.題是什麼?

    計算那一堆商品打折之後的價格總和,結果向上取整,以百為機關

2.思路

    資料格式完整,直接将資料拷到txt中,使用記事本的替換功能将"****"全部去除,"半價"全部替換為"5折","折"全部去除,依次完成這三步之後資料清洗完成,運作如下代碼完成計算,注意,連結生成的exe要與清洗出來的txt檔案處于同一目錄下才可讀取到!

3.ac代碼

答案:5200

#include <stdio.h>

void solve(){
	FILE *in=fopen("in.txt","r");
	
	float ans=0;
	float price,discount;
	while(fscanf(in,"%f%f",&price,&discount)!=EOF){
		if(discount<10) discount*=10;
		ans+=price*discount/100;
	}
	printf("%f\n",ans);
}


int main(){
	solve();
	return 0;
}
           

2.等差素數列

2,3,5,7,11,13,....是素數序列。

類似:7,37,67,97,127,157 這樣完全由素數組成的等差數列,叫等差素數數列。

上邊的數列公差為30,長度為6。

2004年,格林與華人陶哲軒合作證明了:存在任意長度的素數等差數列。

這是數論領域一項驚人的成果!

有這一理論為基礎,請你借助手中的計算機,滿懷信心地搜尋:

長度為10的等差素數列,其公差最小值是多少?

注意:需要送出的是一個整數,不要填寫任何多餘的内容和說明文字。

1.題是什麼?

    首先定義了一種等差素數列:完全由素數組成的等差數列,要求的就是長度為10的公差最小的等差素數列

2.思路

    首先處理這個問題得了解素數處理,沒了解過素數處理的朋友先移步我的部落格 素數埃氏篩法.

    我們知道素數的分布情況是越靠後分布越稀疏,故而滿足條件的公差最小的情況必然會出現在數字偏小的區間,因而我們隻需要将1e6以内的所有素數以埃氏篩法預處理出來,然後在其中尋找滿足條件的最小公差,因是選擇題不必太考慮因而我采取直接暴力搜尋,

    至于為什麼1e6以上的素數集合中不會出現更小公差的10長度等差素數列?我無法理論證明.

3.ac代碼

答案:210

#include <stdio.h>
 
const int maxn=1e6+5;
int prime[maxn],primenum;
int flag[maxn/32+1];//數組大小實際縮小8倍 ,對應位為0表示不是素數 

//通過埃氏篩法擷取t以内的所有素數,存于prime 
void wei_prime(int t){
	primenum=0;
	flag[0]|=3;
	for(int k=1;k<=t/32+1;k++) flag[k]=0;
	for(int i=2;i<=t;i++){
		if(!((flag[i/32]>>(i%32))&1)){
			for(int j=i*2;j<=t;j+=i) flag[j/32]|=(1<<(j%32));
			prime[primenum++]=i;
		}	
	}
}
//通過flag數組判斷數字t是否素數 
bool isprime(int t){
	if((flag[t/32]>>(t%32))&1) return false;
	return true;
}

void solve(){
	wei_prime(1e6);
	int ans=0;
	for(int i=1;i<=1e5;i++){
		for(int k=0;prime[k]+9*i<1e6;k++){
			int j=1;
			while(j<10){
				if(!isprime(prime[k]+i*j)) break;
				j++;
			}
			if(j==10){
				printf("%d\n",i);
				return;
			}
		}
	}
}

int main(){
	solve();
	return 0;
}
           

3.承壓計算

X星球的高科技實驗室中整齊地堆放着某批珍貴金屬原料。

每塊金屬原料的外形、尺寸完全一緻,但重量不同。

金屬材料被嚴格地堆放成金字塔形。

                             7 

                            5 8 

                           7 8 8 

                          9 2 7 2 

                         8 1 4 9 1 

                        8 1 8 8 4 1 

                       7 9 6 1 4 5 4 

                      5 6 5 5 6 9 5 6 

                     5 5 4 7 9 3 5 5 1 

                    7 5 7 9 7 4 7 3 3 1 

                   4 6 4 5 5 8 8 3 2 4 3 

                  1 1 3 3 1 6 6 5 5 4 4 2 

                 9 9 9 2 1 9 1 9 2 9 5 7 9 

                4 3 3 7 7 9 3 6 1 3 8 8 3 7 

               3 6 8 1 5 3 9 5 8 3 8 1 8 3 3 

              8 3 2 3 3 5 5 8 5 4 2 8 6 7 6 9 

             8 1 8 1 8 4 6 2 2 1 7 9 4 2 3 3 4 

            2 8 4 2 2 9 9 2 8 3 4 9 6 3 9 4 6 9 

           7 9 7 4 9 7 6 6 2 8 9 4 1 8 1 7 2 1 6 

          9 2 8 6 4 2 7 9 5 4 1 2 5 1 7 3 9 8 3 3 

         5 2 1 6 7 9 3 2 8 9 5 5 6 6 6 2 1 8 7 9 9 

        6 7 1 8 8 7 5 3 6 5 4 7 3 4 6 7 8 1 3 2 7 4 

       2 2 6 3 5 3 4 9 2 4 5 7 6 6 3 2 7 2 4 8 5 5 4 

      7 4 4 5 8 3 3 8 1 8 6 3 2 1 6 2 6 4 6 3 8 2 9 6 

     1 2 4 1 3 3 5 3 4 9 6 3 8 6 5 9 1 5 3 2 6 8 8 5 3 

    2 2 7 9 3 3 2 8 6 9 8 4 4 9 5 8 2 6 3 4 8 4 9 3 8 8 

   7 7 7 9 7 5 2 7 9 2 5 1 9 2 6 5 3 9 3 5 7 3 5 4 2 8 9 

  7 7 6 6 8 7 5 5 8 2 4 7 7 4 7 2 6 9 2 1 8 2 9 8 5 7 3 6 

 5 9 4 5 5 7 5 5 6 3 5 3 9 5 8 9 5 4 1 2 6 1 4 3 5 3 2 4 1 

X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X 

其中的數字代表金屬塊的重量(計量機關較大)。

最下一層的X代表30台極高精度的電子秤。

假設每塊原料的重量都十分精确地平均落在下方的兩個金屬塊上,

最後,所有的金屬塊的重量都嚴格精确地平分落在最底層的電子秤上。

電子秤的計量機關很小,是以顯示的數字很大。

從業人員發現,其中讀數最小的電子秤的示數為:2086458231

請你推算出:讀數最大的電子秤的示數為多少?

注意:需要送出的是一個整數,不要填寫任何多餘的内容。

1.題是什麼?

    按規則,每塊原料的重量平均落在下方的兩個原料上,已知電子秤顯示存在某個比例的放大,最小的數字被放大為2086458231,你要輸出最大的被顯示為多少.

2.思路

    核心就是每塊原料的重量平均落在下方的兩個原料上,以此對資料進行處理,最終獲得最下方30個的受重,而受重和顯示的數字之間是存在一個比例關系的,這個比例将受重放大了以加大精确度,故而我們需要得到最小和最大的受重,然後根據最小的受重和最小的顯示數字得到這個比例,将之乘以最大的受重,即可知道最大的顯示數字.

3.ac代碼

答案:72665192664

#include <stdio.h>

void solve(){
	int sum[31];
	sum[0]=0;
	for(int i=1;i<=30;i++) sum[i]=i+sum[i-1];
	
	
	double weight[sum[30]];
	FILE *in=fopen("in.txt","r");
	int now=0;
	while(fscanf(in,"%lf",&weight[now])!=EOF) now++;
	for(int i=0;i<30;i++) weight[sum[29]+i]=0;
	
	
	for(int i=1;i<=29;i++){
		for(int j=0;j<i;j++){
			weight[sum[i]+j]+=weight[sum[i-1]+j]*0.5;
			weight[sum[i]+j+1]+=weight[sum[i-1]+j]*0.5;
		}
	}
	double maxnum=0,minnum=1e9;
	for(int i=0;i<30;i++){
		if(maxnum<weight[sum[29]+i]) maxnum=weight[sum[29]+i];
		if(minnum>weight[sum[29]+i]) minnum=weight[sum[29]+i];
	}
	printf("%lf",maxnum*2086458231/minnum);
}


int main(){
	solve();
	return 0;
}
           

4.方格分割

6x6的方格,沿着格子的邊線剪開成兩部分。

要求這兩部分的形狀完全相同。

如圖:p1.png, p2.png, p3.png 就是可行的分割法。

試計算:

包括這3種分法在内,一共有多少種不同的分割方法。

注意:旋轉對稱的屬于同一種分割法。

請送出該整數,不要填寫任何多餘的内容或說明文字。

2017年第八屆藍橋杯C/C++組省賽真題解析1. 購物單2.等差素數列3.承壓計算4.方格分割5.取數位 6.最大公共子串7.日期問題8.包子湊數9.分巧克力 10.k倍區間
2017年第八屆藍橋杯C/C++組省賽真題解析1. 購物單2.等差素數列3.承壓計算4.方格分割5.取數位 6.最大公共子串7.日期問題8.包子湊數9.分巧克力 10.k倍區間
2017年第八屆藍橋杯C/C++組省賽真題解析1. 購物單2.等差素數列3.承壓計算4.方格分割5.取數位 6.最大公共子串7.日期問題8.包子湊數9.分巧克力 10.k倍區間

1.題是什麼?

    如圖,求将6*6的方格分割為完全相同的兩部分的方法數目(旋轉對稱的屬于同一種分割法).

2.思路

    我們先将6*6方格根據最左上方端點建立坐标系,x方向向右,y方向向下,故而他的左上方端點坐标為(0,0),6*6方格的中心點坐标為(3,3);

3.ac代碼

5.取數位

求1個整數的第k位數字有很多種方法。

以下的方法就是一種。

// 求x用10進制表示時的數位長度 
int len(int x){
    if(x<10) return 1;
    return len(x/10)+1;
}
    
// 取x的第k位數字
int f(int x, int k){
    if(len(x)-k==0) return x%10;
    return _____________________;  //填空
}
    
int main()
{
    int x = 23574;
    printf("%d\n", f(x,3));
    return 0;
}
           

對于題目中的測試資料,應該列印5。

請仔細分析源碼,并補充劃線部分所缺少的代碼。

注意:隻送出缺失的代碼,不要填寫任何已有内容或說明性的文字。

1.題是什麼?

    代碼補全

2.思路

    首先len很明了,就是數字是幾位數用的,而f明顯是一個遞歸,已知邏輯就是當x的長度等于k時,傳回x的個位數,其實這就是這個遞歸的終點,我們要填的代碼就是要去逼近這個終點,f(x/10,k)就好了,相當于發現此時數位長了就縮一下就好,這道題的設計者明顯沒有思考過如果給的x的位數本就小于x的情況,故而我們也可以順便額外做一個保護,當出現x的位數本就小于x的情況時傳回-1表示輸入錯誤

3.ac代碼

答案:len(x)<k?-1:f(x/10,k)

#include <stdio.h>
// 求x用10進制表示時的數位長度 
int len(int x){
	if(x<10) return 1;
	return len(x/10)+1;
}
	
// 取x的第k位數字
int f(int x, int k){
	if(len(x)-k==0) return x%10;
	return len(x)<k?-1:f(x/10,k);  //填空
}
	
int main()
{
	int x = 23574;
	printf("%d\n", f(x,3));
	return 0;
}
           

6.最大公共子串

最大公共子串長度問題就是:

求兩個串的所有子串中能夠比對上的最大長度是多少。

比如:"abcdkkk" 和 "baabcdadabc",

可以找到的最長的公共子串是"abcd",是以最大公共子串長度為4。

下面的程式是采用矩陣法進行求解的,這對串的規模不大的情況還是比較有效的解法。

請分析該解法的思路,并補全劃線部分缺失的代碼。

#include <stdio.h>
#include <string.h>

#define N 256
int f(const char* s1, const char* s2)
{
	int a[N][N];
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	int i,j;
	
	memset(a,0,sizeof(int)*N*N);
	int max = 0;
	for(i=1; i<=len1; i++){
		for(j=1; j<=len2; j++){
			if(s1[i-1]==s2[j-1]) {
				a[i][j] = __________________________;  //填空
				if(a[i][j] > max) max = a[i][j];
			}
		}
	}
	
	return max;
}

int main()
{
	printf("%d\n", f("abcdkkk", "baabcdadabc"));
	return 0;
}
           

注意:隻送出缺少的代碼,不要送出已有的代碼和符号。也不要送出說明性文字。

1.題是什麼?

    代碼補全

2.思路

    題中代碼是想使用類似與dp的思路,a[i][j]的含義是s1串中前i個字元所構成子串與s2串中前j個字元所構成子串的最長公共字尾長度,正是以,如果s1[i]==s2[j],我們便可以以a[i][j] = a[i-1][j-1]+1;求得a[i][j],這樣便完成了資料的遞推.最終a數組中最大的值便是最長公共子串長度.

    你可能會有點迷糊,我們要求的是s1與s2的最長公共子串長度啊,關最長公共字尾長度什麼事?其實隻要我們求出所有m*n個情況下的最長公共字尾,取其最大值便是最長公共子串長度,就如abcdkkk與baabcdadabc,最長公共子串長度就是a[3][5]的值.

3.ac代碼

答案:a[i-1][j-1]+1

#include <stdio.h>
#include <string.h>

#define N 256
int f(const char* s1, const char* s2)
{
	int a[N][N];
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	int i,j;
	
	memset(a,0,sizeof(int)*N*N);
	int max = 0;
	for(i=1; i<=len1; i++){
		for(j=1; j<=len2; j++){
			if(s1[i-1]==s2[j-1]) {
				a[i][j] = a[i-1][j-1]+1;  //填空
				if(a[i][j] > max) max = a[i][j];
			}
		}
	}
	
	return max;
}

int main()
{
	printf("%d\n", f("abcdkkk", "baabcdadabc"));
	return 0;
}
           

7.日期問題

小明正在整理一批曆史文獻。這些曆史文獻中出現了很多日期。小明知道這些日期都在1960年1月1日至2059年12月31日。令小明頭疼的是,這些日期采用的格式非常不統一,有采用年/月/日的,有采用月/日/年的,還有采用日/月/年的。更加麻煩的是,年份也都省略了前兩位,使得文獻上的一個日期,存在很多可能的日期與其對應。  

比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。  

給出一個文獻上的日期,你能幫助小明判斷有哪些可能的日期對其對應嗎?

輸入

----

一個日期,格式是"AA/BB/CC"。  (0 <= A, B, C <= 9)  

輸入

----

輸出若幹個不相同的日期,每個日期一行,格式是"yyyy-MM-dd"。多個日期按從早到晚排列。  

樣例輸入

----

02/03/04  

樣例輸出

----

2002-03-04  

2004-02-03  

2004-03-02  

資源約定:

峰值記憶體消耗(含虛拟機) < 256M

CPU消耗  < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘内容。

注意:

main函數需要傳回0;

隻使用ANSI C/ANSI C++ 标準;

不要調用依賴于編譯環境或作業系統的特殊函數。

所有依賴的函數必須明确地在源檔案中 #include <xxx>

不能通過工程設定而省略常用頭檔案。

送出程式時,注意選擇所期望的語言類型和編譯器類型。

1.題是什麼?

2.思路

3.ac代碼

8.包子湊數

小明幾乎每天早晨都會在一家包子鋪吃早餐。他發現這家包子鋪有N種蒸籠,其中第i種蒸籠恰好能放Ai個包子。每種蒸籠都有非常多籠,可以認為是無限籠。

每當有顧客想買X個包子,賣包子的大叔就會迅速選出若幹籠包子來,使得這若幹籠中恰好一共有X個包子。比如一共有3種蒸籠,分别能放3、4和5個包子。當顧客想買11個包子時,大叔就會選2籠3個的再加1籠5個的(也可能選出1籠3個的再加2籠4個的)。

當然有時包子大叔無論如何也湊不出顧客想買的數量。比如一共有3種蒸籠,分别能放4、5和6個包子。而顧客想買7個包子時,大叔就湊不出來了。

小明想知道一共有多少種數目是包子大叔湊不出來的。

輸入

----

第一行包含一個整數N。(1 <= N <= 100)

以下N行每行包含一個整數Ai。(1 <= Ai <= 100)  

輸出

----

一個整數代表答案。如果湊不出的數目有無限多個,輸出INF。

例如,

輸入:

2  

4  

5   

程式應該輸出:

6  

再例如,

輸入:

2  

4  

6    

程式應該輸出:

INF

1.題是什麼?

    給你n個數字,問你通過這n個數字湊不出來的數字有多少個,無窮個則輸出INF

2.思路

   首先判斷這n籠包子是否存在某兩個數互質,如果存在則直接輸出INF,否則再通過完全背包dp求解具體個數,這是主體思路.

    判斷互質其實就是解其最大公約數看是否是1,求解兩數最大公約數用gcd算法,不了解gcd算法的移步我的部落格 gcd與擴充gcd

   那為什麼存在互質的情況下能湊出來的數目會是固定的呢?我們來想想通過n籠包子湊出目标數目target的過程,其實就相當于求解以下方程:a[0]*x1+a[1]*x2+...+a[n-1]*xn=target,找是否存在這麼一組解x1,x2..xn使之成立,成立則能湊出來

    而如果a[0]...a[n-1]這n籠包子中存在某兩個數如a[0],a[3]是互質的,則大于a[0]*a[3]的所有數我都可以僅以a[0]和a[3]湊出來,而如果不存在互質,在無限大的資料下不能湊出的數字一定無窮多個..

    因每籠包子最多100個,故隻需要處理10000以内的即可,判斷這些數字是否可由這n籠湊成其實就是一個标準的完全背包問題.

3.ac代碼

#include <stdio.h>

int gcd(int a,int b){
	if(b==0) return a;
	return gcd(b,a%b);
}

const int maxn=105;
int a[maxn];
void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	
	int zhishu=a[0];
	for(int i=1;i<n;i++) zhishu=gcd(zhishu,a[i]); 
	
	if(zhishu!=1){
		printf("INF");
		return;
	}
	else{
		bool dp[10001];//dp[i]=true表示i個包子的需求能被湊到 
		dp[0]=true;
		for(int i=1;i<=1e4;i++) dp[i]=false;
		
		for(int i=0;i<n;i++){
			for(int j=0;j+a[i]<=1e4;j++){
				if(dp[j]) dp[j+a[i]]=true;
			}
		}
		
		int ans=0;
		for(int i=1;i<=1e4;i++) if(!dp[i]) ans++;
		printf("%d",ans);
	}
}
	
int main(){
	solve();
	return 0;
}
           

9.分巧克力

    兒童節那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友們。

    小明一共有N塊巧克力,其中第i塊是Hi x Wi的方格組成的長方形。

    為了公平起見,小明需要從這 N 塊巧克力中切出K塊巧克力分給小朋友們。切出的巧克力需要滿足:

    1. 形狀是正方形,邊長是整數  

    2. 大小相同  

例如一塊6x5的巧克力可以切出6塊2x2的巧克力或者2塊3x3的巧克力。

當然小朋友們都希望得到的巧克力盡可能大,你能幫小Hi計算出最大的邊長是多少麼?

輸入

第一行包含兩個整數N和K。(1 <= N, K <= 100000)  

以下N行每行包含兩個整數Hi和Wi。(1 <= Hi, Wi <= 100000) 

輸入保證每位小朋友至少能獲得一塊1x1的巧克力。   

輸出

輸出切出的正方形巧克力最大可能的邊長。

樣例輸入:

2 10  

6 5  

5 6  

樣例輸出:

2

資源約定:

峰值記憶體消耗(含虛拟機) < 256M

CPU消耗  < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘内容。

注意:

main函數需要傳回0;

隻使用ANSI C/ANSI C++ 标準;

不要調用依賴于編譯環境或作業系統的特殊函數。

所有依賴的函數必須明确地在源檔案中 #include <xxx>

不能通過工程設定而省略常用頭檔案。

送出程式時,注意選擇所期望的語言類型和編譯器類型。

1.題是什麼?

    要從已知大小的n塊巧克力中切出k塊正方形巧克力,問可取的最大大小是多少.

2.思路

    一道最大化極小值的問題,使用二分查值确定這個最大大小,不了解二分查值的移步我的部落格: 二分查值法

    這裡由于目标答案是整數需要做一些改動我的模版代碼才能用,c函數做的就是判斷在答案為mid時從n個蛋糕中切出的mid大小正方形巧克力夠不夠k個.

3.ac代碼

#include <stdio.h>
const int maxn=1e5+5;
const int maxhw=1e5;

int n,k;
int h[maxn],w[maxn];

bool c(int mid){
    //判斷答案為mid時是否滿足條件,滿足傳回true,否則false

    long long sum=0;
    for(int i=0;i<n;i++) sum+=(h[i]/mid)*(w[i]/mid);
    
    if(sum>=k) return true;
    return false;
}

void solve(){
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++) scanf("%d%d",&h[i],&w[i]); 
	
	int l=1,r=maxhw+1,mid;
	while(r-l>1){
		mid=(l+r)/2;
		if(c(mid)) l=mid; 
		else r=mid;
	}
	printf("%d",l);
}


int main(){
	solve();
	return 0;
}
           

10.k倍區間

給定一個長度為N的數列,A1, A2, ... AN,如果其中一段連續的子序列Ai, Ai+1, ... Aj(i <= j)之和是K的倍數,我們就稱這個區間[i, j]是K倍區間。  

你能求出數列中總共有多少個K倍區間嗎?  

輸入

-----

第一行包含兩個整數N和K。(1 <= N, K <= 100000)  

以下N行每行包含一個整數Ai。(1 <= Ai <= 100000)  

輸出

-----

輸出一個整數,代表K倍區間的數目。  

例如,

輸入:

5 2

1  

2  

3  

4  

5  

程式應該輸出:

6

資源約定:

峰值記憶體消耗(含虛拟機) < 256M

CPU消耗  < 2000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入...” 的多餘内容。

注意:

main函數需要傳回0;

隻使用ANSI C/ANSI C++ 标準;

不要調用依賴于編譯環境或作業系統的特殊函數。

所有依賴的函數必須明确地在源檔案中 #include <xxx>

不能通過工程設定而省略常用頭檔案。

送出程式時,注意選擇所期望的語言類型和編譯器類型。

1.題是什麼?

    題意明了,讓你統計在所給長度1e5的數組中,區間和為k的倍數的子區間數目

2.思路

    首先,最簡單的思路,預處理字首和,然後做二重循環對将近n*n個子區間一一求餘判斷這個子區間是否為0,這樣的複雜度為O(n^2),會爆.

    其次,我的思路,既然我隻是要知道區間和為k倍數的子區間數目而不是到底有哪些子區間,那我豈不是隻需要對這些子區間做一個記憶化,保留下對我後續計算有用的特征就好了嗎,那麼一個區間的什麼特征對我後續判斷其它區間區間和是否能被k整除有決定性作用呢?

    tips:(記憶化是什麼? 我的了解:記憶化就是以某種方式存儲你計算過的資料,後面加以利用,優化計算效率)

                                                          自然是這個區間求餘k後的結果!

    現在,我們知道能通過這個特征去記憶化壓縮資料之後,我們該怎麼寫進算法裡并優化統計方法呢?

    自然是做一個字首和結果求餘k的數組sum和一個記憶化數組num,記錄之前出現過的所有字首和求餘k的結果的情況,這樣的話之後當我們統計以a[i]結尾的區間有多少個滿足題意時,num[sum[i]]即為答案!,為什麼呢? 因為你想如果k=5,sum[i]=3,以a[i]為結尾滿足題意的情況數目不就是前面的sum中為3的個數嗎,我這一整個區間多了3,那我丢掉某個正好多了3的字首不就不多了嗎!

      正好就是我們的num[3],為了維護num數組,每次要num[sum[i]]+=1,将本次情況更新進記憶化數組.

3.知識精煉

    有沒有注意到為什麼num被叫做記憶化數組?因為他裡面存的某個數字比如num[2]=3的含義是,字首和求餘結果為2的情況目前有3種,他是一種對于過去所有情況的記憶,僅僅根據最核心的特征進行記憶,這就是記憶化!你,get到feel了嗎?

4.ac代碼

#include <stdio.h>
 
int maxn=1e5+5;
int maxk=1e5+5;
 
int main(){
	int a[maxn];
	int sum[maxn]; //字首和求餘k結果,sum[i]表示(a[0]+a[1]+...+a[i])%k
	int num[maxk];//記憶化,num[i]=x表示在目前已出現x個和為i的字首
	
	int n,k,i;
	scanf("%d%d",&n,&k);
	for(i=0;i<n;i++) scanf("%d",&a[i]);
	//初始化字首和 
	sum[0]=a[0]%k;
	for(i=1;i<n;i++) sum[i]=(a[i]+sum[i-1])%k;
	//初始化記憶數組
	num[0]=1; //初始狀态應該包含空字首情況!,對後面的狀态遞推異常重要
	for(i=1;i<maxk;i++) num[i]=0;
	
	long long ans=0; 
	//線性同時記憶化 
	for(i=0;i<n;i++) ans+=num[sum[i]]++; 
	
	printf("%lld\n",ans);
	return 0;
}
           

繼續閱讀