2013藍橋杯題解c組C++

bread and coffee發表於2020-11-06

01 猜年齡

題目描述:
美國數學家維納(N.Wiener)智力早熟,11歲就上了大學。他曾在1935~1936年應邀來中國清華大學講學。
一次,他參加某個重要會議,年輕的臉孔引人注目。於是有人詢問他的年齡,他回答說:
“我年齡的立方是個4位數。我年齡的4次方是個6位數。這10個數字正好包含了從0到9這10個數字,每個都恰好出現1次。”
請你推算一下,他當時到底有多年輕。

解題思路:

根據題意選取可能的年齡範圍(可選取[10, 100]),使用一層迴圈一次列舉,找到符合要求的解即可。注意對數位的分離、位數判定、排序等操作

程式碼:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int main()
{
	int i,j;
	int n1,n2;    //n1.n2分別儲存i^3與i^4
	int digit[10],len;    //digit儲存n1.n2中的各數位 len記錄位數
	int suc;      //suc標記i是否符合要求 
	for(i=10;i<=100;i++)  //列舉年齡i 
	{
		n1=i*i*i;         
		n2=i*i*i*i;
		len=0; 
		while(n1)
		{
			digit[len++]=n1%10;
			n1/=10;
		}
		if(len==4)   //n1是4位數,繼續 
		{
			while(n2)
			{
				digit[len++]=n2%10;
				n2/=10;
			}
			if(len==10)   //n2是6位數,繼續 
			{
				sort(digit,digit+len);  //0,1,2,...,9 
				suc=1;
				for(j=0;j<len;j++) 
				{
					if(digit[j]!=j)
					{
						suc=0;
						break;
					}
				}
				if(suc==1)   //符合要求,輸出 
					printf("%d %d %d\n",i,i*i*i,i*i*i*i);
			}
		}
	}
	return 0;
}

執行結果:

18 5832 104976

答案:

18

02 馬虎的算式

題目描述:
小明是個急性子,上小學的時候經常把老師寫在黑板上的題目抄錯了。
有一次,老師出的題目是:36 x 495 = ?
他卻給抄成了:396 x 45 = ?
但結果卻很戲劇性,他的答案竟然是對的!!
因為 36 * 495 = 396 * 45 = 17820
類似這樣的巧合情況可能還有很多,比如:27 * 594 = 297 * 54
假設 a b c d e 代表1~9不同的5個數字(注意是各不相同的數字,且不含0)
能滿足形如: ab * cde = adb * ce 這樣的算式一共有多少種呢?

請你利用計算機的優勢尋找所有的可能,並回答不同算式的種類數。
滿足乘法交換律的算式計為不同的種類,所以答案肯定是個偶數。
答案直接通過瀏覽器提交。
注意:只提交一個表示最終統計種類數的數字,不要提交解答過程或其它多餘的內容。

程式碼:

#include <stdio.h>
int main()
{
    int a,b,c,d,e,n;
    n=0;
    for (a=1;a<10;a++)
    for (b=1;b<10;b++)
       { 
        if (a==b) continue;
             else 
                for (c=1;c<10;c++)
               {  
               if((c==a)||(c==b)) continue;
                      else 
                         for (d=1;d<10;d++)
                       { 
                           if((d==a) || (d==b) || (d==c)) continue ;
                              else
                                  for (e=1;e<10;e++)
                                   { 
                                   if ((e==a)||(e==b)||(e==c)||(e==d)) continue;
                                         else if ( (10*a+b)*(100*c+10*d+e)==(100*a+10*d+b)*(10*c+e)) 
                                             n++;
                                    }
                       }
               }
       }
 
    printf("%d\n",n);
    return 0;
}

執行結果:

142

03 振興中華

題目描述
小明參加了學校的趣味運動會,其中的一個專案是:跳格子。
地上畫著一些格子,每個格子裡寫一個字,如下所示:
從我做起振
我做起振興
做起振興中
起振興中華
比賽時,先站在左上角的寫著“從”字的格子裡,可以橫向或縱向跳到相鄰的格子裡,但不能跳到對角的格子或其它位置。一直要跳到“華”字結束。
要求跳過的路線剛好構成“從我做起振興中華”這句話。
請你幫助小明算一算他一共有多少種可能的跳躍路線呢?

答案是一個整數,請通過瀏覽器直接提交該數字。
注意:不要提交解答過程,或其它輔助說明類的內容。

解題思路:

把每個格子都換成一個數字,從0算起,這樣,題目就變成了從0格跳到第7格的路線數目

程式碼:

#include <stdio.h>
int a[4][5];
int sum;
void dfs(int row,int col,int index)
{
	//if(a[row][col]!=index)  這兩句話其實沒必要,感謝 hzylmf 提的意見 
	//	return ;
	if(a[row][col]==index && index==7)
		sum++;
	else {
		if(row+1<4)
				dfs(row+1,col,index+1);
		if(col+1<5)
				dfs(row,col+1,index+1);
	}
}
int main()
{
	int row,col;
	for(row=0;row<4;row++)
		for(col=0;col<5;col++)
			a[row][col]=row+col;
 
	dfs(0,0,0);
	printf("sum = %d\n",sum);
 
	return 0;
}

執行結果:

35

04 幻方填空

題目描述:
幻方是把一些數字填寫在方陣中,使得行、列、兩條對角線的數字之和都相等。
歐洲最著名的幻方是德國數學家、畫家迪勒創作的版畫《憂鬱》中給出的一個4階幻方。
他把1,2,3,…16 這16個數字填寫在4 x 4的方格中。
如圖所示,即:

16 ? ? 13
? ? 11 ?
9 ? ? *
? 15 ? 1
表中有些數字已經顯露出來,還有些用?和*代替。
請你計算出? 和 * 所代表的數字。並把 * 所代表的數字作為本題答案提交。

答案是一個整數,請通過瀏覽器直接提交該數字。
注意:不要提交解答過程,或其它輔助說明類的內容。

解題思路:

搜尋(dfs)+剪枝
可以使用一個二維陣列儲存該幻方(預先沒有填數的位置(包括?和*)為0),然後從第一個“0”開始(位置(0, 1))搜尋,依次試探2~16 15個數(因為1已經預先填入幻方)。找到符合要求的解。

注意標記及剪枝(這裡使用了行剪枝,當搜尋到第二行時,用flag記錄第一行4個數的和,後面若發現某行4個數之和不等於flag,則該方案不符合要求),以提高程式效率。

程式碼:

#include <stdio.h>
#include <string.h>
#define maxn 20
int mat[4][4]={    //4*4幻方(0代表?或*) 
	{16,0,0,13},
	{0,0,11,0},
	{9,0,0,0},
	{0,15,0,1}
};
int used[maxn];    //16個數的使用標記 1-已使用 0-未使用 
int flag;          //記錄第一行4個數之和 
int Judge(int mt[][4]) //判斷各列與兩條對角線之和是否與各行之和相等 
{
	int i,j;
	int sumc,sumln1,sumln2;
	//依次計算各列4個數之和 
	for(j=0;j<4;j++)
	{
		sumc=0; 
		for(i=0;i<4;i++)
			sumc+=mt[i][j];
		//若sumc不等於flag,則方案不成立 
		if(sumc!=flag)
			return 0;
	}
	//計算兩條對角線上4個數之和 
	sumln1=sumln2=0;
	for(i=0;i<4;i++)
	{
		for(j=0;j<4;j++)
		{
			//主對角線 
			if(i==j)
				sumln1+=mt[i][j];
			//副對角線 
			if(i==4-j-1)
				sumln2+=mt[i][j];
		}
	}
	//若sumln1或sumln2不等於flag,則方案不成立 
	if(sumln1!=flag || sumln2!=flag)
		return 0;
	//否則方案成立 
	return 1;
}
void dfs(int x,int y,int cursumr)
{
	int i,j,num;
	//搜尋終點
	if(x==3 && y==4)
	{
		if(Judge(mat))
		{
			for(i=0;i<4;i++)
			{
				for(j=0;j<3;j++)
					printf("%d ",mat[i][j]);
				printf("%d\n",mat[i][3]);
			}
		}
		return;
	} 
	//一行搜尋結束:繼續搜尋下一行
	if(y==4)
		dfs(x+1,0,0);
	//(x,y)已填入數字,則繼續搜尋下一位置(x,y+1) 
	if(mat[x][y]!=0)
		dfs(x,y+1,cursumr+mat[x][y]);
	//計算第一行4個數之和,並以此作為後續判斷的標記 
	if(x==1 && y==0)
	{
		flag=0;
		for(j=0;j<4;j++)
			flag+=mat[0][j];
	}
	//依次試探2-16 15個數 
	for(num=2;num<=16;num++)
	{
		//當前位置(x,y)沒有填數,並且num未使用 
		if(mat[x][y]==0 && !used[num])
		{
			//行剪枝:搜尋到每行最後一個數時,若發現cursumr+待填數num不等於flag
			//則num無法填入,應試探下一個數num+1 
			if(x>=1 && y==3 && cursumr+num!=flag)
				continue;
			//填入 
			used[num]=1;
			mat[x][y]=num;
			//搜尋下一位置 
			dfs(x,y+1,cursumr+mat[x][y]);
			//回溯 
			used[num]=0;
			mat[x][y]=0;
		}
	}
}
int main()
{
	//初始化 
	memset(used,0,sizeof(used));
	//1.9.11.13.15.16已填入幻方,因此置used標記1 
	used[1]=1,used[9]=1,used[11]=1;
	used[13]=1,used[15]=1,used[16]=1;
	//從第一個問號(0,1)處開始搜尋 
	dfs(0,1,16);
	return 0;
}

執行結果:

16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1

答案:

12

05 公約數公倍數

題目描述:
我們經常會用到求兩個整數的最大公約數和最小公倍數的功能。
下面的程式給出了一種演算法。
函式 myfunc 接受兩個正整數a,b
經過運算後列印出 它們的最大公約數和最小公倍數。
此時,呼叫 myfunc(15,20)

將會輸出:
5
60

// 交換數值
void swap(int *a,int *b)
{
   int temp;
   temp=*a;
   *a=*b;
   *b=temp;
}

void myfunc(int a, int b)
{
   int m,n,r;  
   if(a<b) swap(&a,&b);
   m=a;n=b;r=a%b;
   while(r!=0)
   {
    a=b;b=r;
    r=a%b;
   }
   printf("%d\n",b);  // 最大公約數 
   printf("%d\n", ____________________________________);  // 最小公倍數 
}

請分析程式碼邏輯,並推測劃線處的程式碼,通過網頁提交。
注意:僅把缺少的程式碼作為答案,千萬不要填寫多餘的程式碼、符號或說明文字!!

解題思路:
最大公約數gcd與最小公倍數lcm 使用輾轉相除法求解,需要注意第一個數a比第二個數b時才可使用此法(因此當a>b時進行預處理,交換兩數)。

答案:

(m*n)/b

除錯程式碼:

#include <stdio.h>
// 交換數值
void swap(int *a,int *b)
{
   int temp;
   temp=*a;
   *a=*b;
   *b=temp;
}
 
void myfunc(int a, int b)
{
   int m,n,r;  
   if(a<b) swap(&a,&b);
   m=a;n=b;r=a%b;
   while(r!=0)
   {
    a=b;b=r;
    r=a%b;
   }
   printf("%d\n",b);  // 最大公約數 
   printf("%d\n",(m*n)/b);  // 最小公倍數 
}
 
int main()
{
	int n1,n2;
	scanf("%d %d",&n1,&n2);
	myfunc(n1,n2);
	return 0;
}

06 三部排序

題目描述:
一般的排序有許多經典演算法,如快速排序、希爾排序等。
但實際應用時,經常會或多或少有一些特殊的要求。我們沒必要套用那些經典演算法,可以根據實際情況建立更好的解法。
比如,對一個整型陣列中的數字進行分類排序:
使得負數都靠左端,正數都靠右端,0在中部。注意問題的特點是:負數區域和正數區域內並不要求有序。可以利用這個特點通過1次線性掃描就結束戰鬥!!
以下的程式實現了該目標。
其中x指向待排序的整型陣列,len是陣列的長度。

void sort3p(int* x, int len)
{
int p = 0;
int left = 0;
int right = len-1;
 
while(p<=right){
if(x[p]<0){
int t = x[left];
x[left] = x[p];
x[p] = t;
left++;
p++;
}
else if(x[p]>0){
int t = x[right];
x[right] = x[p];
x[p] = t;
right--; 
}
else{
__________________________;  //填空位置
}
}
 
}

如果給定陣列:
25,18,-2,0,16,-5,33,21,0,19,-16,25,-3,0
則排序後為:
-3,-2,-16,-5,0,0,0,21,19,33,25,16,18,25

請分析程式碼邏輯,並推測劃線處的程式碼,通過網頁提交
注意:僅把缺少的程式碼作為答案,千萬不要填寫多餘的程式碼、符號或說明文字!!

解題思路:
排序演算法的改進實現
基本思想:陣列中的0將整個陣列分成3部分,所有小於0的數放到0的前面,所有大於0的數放到0的後面。

遇到0的時候,忽略之,繼續處理下一個數。由於這裡的left指標不動,還是指向0的前一個負數,所以碰到小於0的數的時候又會把前面忽略的0交換過來,這樣就會把所有0堆到後面來。最後跳過所有0,直到p>right結束。

答案:

p++

除錯程式碼:

#include <stdio.h>
#define maxn 1010
int n;
int a[maxn];
void sort3p(int* x, int len)
{
	int p = 0;
	int left = 0;
	int right = len-1;
	
	while(p<=right){
		if(x[p]<0){
			int t = x[left];
			x[left] = x[p];
			x[p] = t;
			left++;
			p++;
		}
		else if(x[p]>0){
			int t = x[right];
			x[right] = x[p];
			x[p] = t;
			right--;			
		}
		else{
			p++;  //填空位置
		}
	}
}
int main()
{
	int i;
	scanf("%d",&n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	sort3p(a,n);
	for(i=0;i<n-1;i++)
		printf("%d ",a[i]);
	printf("%d\n",a[n-1]);
	return 0;
}

07 核桃的數量

題目描述:
小張是軟體專案經理,他帶領3個開發組。工期緊,今天都在加班呢。為鼓舞士氣,小張打算給每個組發一袋核桃(據傳言能補腦)。他的要求是:
1. 各組的核桃數量必須相同
2. 各組內必須能平分核桃(當然是不能打碎的)
3. 儘量提供滿足1,2條件的最小數量(節約鬧革命嘛)
程式從標準輸入讀入:
a b c
a,b,c都是正整數,表示每個組正在加班的人數,用空格分開(a,b,c<30)

程式輸出:
一個正整數,表示每袋核桃的數量。

樣例輸入1:

2 4 5

樣例輸出1:

20

樣例輸入2:

3 1 1

樣例輸出2:

3

資源約定:
峰值記憶體消耗(含虛擬機器) < 64M
CPU消耗 < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入…” 的多餘內容。
所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include , 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

解題思路: gcd&lcm問題
根據要求1. 2. -> 三個數的公倍數(滿足都能平分)
根據要求3. -> 三個數的最小公倍數(滿足"最小")
由此可確定題目模型:求a, b, c三個數的最小公倍數
求解時,首先求出a, b兩個數的最小公倍數temp,然後求temp, c兩個數的最小公倍數即可。這裡使用遞迴實現。

程式碼:


#include <stdio.h>
int gcd(int n1,int n2)
{
	if(n2==0)
		return n1;
	return gcd(n2,n1%n2);
}
int lcm3(int a,int b,int c)
{
	int temp=a*b/gcd(a,b);
	return temp*c/gcd(temp,c);
}
int main()
{
	int a,b,c;
	scanf("%d %d %d",&a,&b,&c);
	printf("%d\n",lcm3(a,b,c));
	return 0;
}

08 列印十字圖

題目描述:
小明為某機構設計了一個十字型的徽標(並非紅十字會啊),如下所示
在這裡插入圖片描述
對方同時也需要在電腦dos視窗中以字元的形式輸出該標誌,並能任意控制層數。
為了能準確比對空白的數量,程式要求對行中的空白以句點(.)代替。

輸入格式:
一個正整數 n (n<30) 表示要求列印圖形的層數

輸出格式:
對應包圍層數的該標誌。

樣例輸入1:

1

樣例輸出1:

..$$$$$..
..$...$..
$$$.$.$$$
$...$...$
$.$$$$$.$
$...$...$
$$$.$.$$$
..$...$..
..$$$$$..

樣例輸入2:

3

樣例輸出2:

..$$$$$$$$$$$$$..
..$...........$..
$$$.$$$$$$$$$.$$$
$...$.......$...$
$.$$$.$$$$$.$$$.$
$.$...$...$...$.$
$.$.$$$.$.$$$.$.$
$.$.$...$...$.$.$
$.$.$.$$$$$.$.$.$
$.$.$...$...$.$.$
$.$.$$$.$.$$$.$.$
$.$...$...$...$.$
$.$$$.$$$$$.$$$.$
$...$.......$...$
$$$.$$$$$$$$$.$$$
..$...........$..
..$$$$$$$$$$$$$..

請仔細觀察樣例,尤其要注意句點的數量和輸出位置。

資源約定:
峰值記憶體消耗 < 64M
CPU消耗 < 1000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入…” 的多餘內容。
所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include , 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

解題思路: 文字圖形問題
一般地,此類圖形都有一定的規律,通常有兩種主要解法:①遞迴 ②抓住規律(對稱性、週期性等是經常出現的),迴圈填圖。
這裡使用②即可,關鍵是圖形中的規律。

程式碼:

#include <stdio.h>
#include <string.h>
#define maxlen 130
int n;
char area[maxlen][maxlen];
int main()
{
	int i,j;
	int len,flag;
	scanf("%d",&n);
	//初始化標誌 
	memset(area,'.',sizeof(area));
	//確定(正方形)區域長度 
	len=4*n+5;
	//填充區域I 
	for(i=0;i<=len/2;i+=2)
		for(j=i+2;j<=len/2;j++)
			area[i][j]='$';
	//填充區域II
	for(j=0;j<=len/2;j+=2)
		for(i=j+2;i<=len/2;i++)
			area[i][j]='$';
	//填充區域III
	for(i=len/2,j=len/2;i>=2,j>=2;i-=2,j-=2)
	{
		area[i][j]='$';
		area[i][j-1]='$';
		area[i-1][j]='$';
	}
	//左右對稱
	for(i=0;i<=len/2;i++)
		for(j=1;j<=len/2;j++)
			area[i][len/2+j]=area[i][len/2-j];
	//上下對稱
	for(i=1;i<=len/2;i++)
		for(j=0;j<len;j++)
			area[len/2+i][j]=area[len/2-i][j];
	//輸出該標誌 
	for(i=0;i<len;i++)
	{
		for(j=0;j<len-1;j++)
			printf("%c",area[i][j]);
		printf("%c\n",area[i][len-1]);
	}
	return 0;
}

09 帶分數

題目描述:
100 可以表示為帶分數的形式:100 = 3 + 69258 / 714
還可以表示為:100 = 82 + 3546 / 197
注意特徵:帶分數中,數字1~9分別出現且只出現一次(不包含0)。
類似這樣的帶分數,100 有 11 種表示法。

題目要求:
從標準輸入讀入一個正整數N (N<1000*1000)
程式輸出該數字用數碼1~9不重複不遺漏地組成帶分數表示的全部種數。
注意:不要求輸出每個表示,只統計有多少表示法!

樣例輸入1:

100

樣例輸出1:

11

樣例輸入2:

105

樣例輸出2:

6

資源約定:
峰值記憶體消耗 < 64M
CPU消耗 < 3000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入…” 的多餘內容。
所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include , 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

題目描述:
列舉+全排列 / 搜尋(dfs)
根據題意可確定帶分數的基本形式:N= num1 + num2 / num3。
因此題目的關鍵即確定num1, num2, num3三個數。
又由於帶分數中1~9 9個數是隨機出現的,因此可通過全排列使9個數"隨機滾動",通過迴圈(或dfs),將全排列中的9個數分成3組,以確定num1,
num2與num3。當N= num1 + num2 / num3並且num2能整除num3時,才是符合要求的解。
為提高程式執行效率,可進行一定的優化。

程式碼:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int N;
int ans=0;
int a[9]={1,2,3,4,5,6,7,8,9}; 
int getDigit(int n)   //獲取n的位數 
{
	int temp=n;
	int len=0;
	while(temp!=0)
	{
		len++;
		temp/=10;
	}
	return len;
}
int main()
{
	int i,j,k;
	int num1,num2,num3;
	int digit,result;
	scanf("%d",&N);
	digit=getDigit(N);
	do
	{
		//使用i,j確定num1與num2 num2與num3的分界點 
		for(i=0;i<digit;i++)
		{
			for(j=i+1;j<8;j++)
			{
				num1=num2=num3=0;
				for(k=0;k<=i;k++)   //累加求num1 
					num1=num1*10+a[k];
				if(num1>N)    //優化點:當num1比N大時,即使num2/num3再小,也不符合要求,故這個解不合題意 
					continue;
				for(k=i+1;k<=j;k++) //累加求num2 
					num2=num2*10+a[k];
				for(k=j+1;k<9;k++)  //累加求num3 
					num3=num3*10+a[k];
				result=num1+num2/num3;
				if(num2%num3==0 && result==N)  //注意結果為N並且num2能整除num3時,才是我們需要的解 
					ans++;
			}
		}
	} while(next_permutation(a,a+9));
	printf("%d\n",ans);
	return 0;
}

10 剪格子

題目描述:
如圖所示,3 x 3 的格子中填寫了一些整數
![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201106195053630.png#pic_center
我們沿著圖中的紅色線剪開,得到兩個部分,每個部分的數字和都是60。
本題的要求就是請你程式設計判定:對給定的m x n 的格子中的整數,是否可以分割為兩個部分,使得這兩個區域的數字和相等。
如果存在多種解答,請輸出包含左上角格子的那個區域包含的格子的最小數目。
如果無法分割,則輸出 0

程式輸入輸出格式要求:

程式先讀入兩個整數 m n 用空格分割 (m,n<10)
表示表格的寬度和高度
接下來是n行,每行m個正整數,用空格分開。每個整數不大於10000
程式輸出:在所有解中,包含左上角的分割區可能包含的最小的格子數目。

樣例輸入1:

3 3
10 1 52
20 30 1
1 2 3

樣例輸出1:

3

樣例輸入2:

4 3
1 1 1 1
1 30 80 2
1 1 1 100

樣例輸出2:

10

(參見下圖)
在這裡插入圖片描述

資源約定:
峰值記憶體消耗 < 64M
CPU消耗 < 5000ms

請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入…” 的多餘內容。
所有程式碼放在同一個原始檔中,除錯通過後,拷貝提交該原始碼。

注意: main函式需要返回0
注意: 只使用ANSI C/ANSI C++ 標準,不要呼叫依賴於編譯環境或作業系統的特殊函式。
注意: 所有依賴的函式必須明確地在原始檔中 #include , 不能通過工程設定而省略常用標頭檔案。

提交時,注意選擇所期望的編譯器型別。

解題思路:
題意:從格子的左上角開始,沿上下左右4個方向,將n * m的格子分成兩部分,使兩部分的數字之和相等。
也就是說當發現當前搜尋的部分數字之和為格子數字總和的一半時,該組解符合題意。在所有符合題意的解中尋找最優解。
因為要尋找最優解,故可以根據問題情境進行"剪枝",以提高程式執行效率。以下關鍵點可供參考:
(1)預處理:輸入的同時求n*m格子區域中所有數字之和sum。若sum不是偶數,則一定不能分成符合要求的兩部分,無解;
(2)搜尋過程中,若發現當前格子數目超過了最優解,則繼續搜尋下去,即使能夠找到解,也一定不是最優解。因此不必繼續向後搜尋,可以另謀其他方案。

程式碼:

#include <stdio.h>
#include <math.h>
#include <string.h>
#define maxm 15
#define maxn 15
#define INF pow(2,31)-1 //初始化minans 
int m,n;
int sum=0,minans=INF;   //sum-n*m區域中數字之和 minans-所求最優解 
int map[maxn][maxm],vis[maxn][maxm];   //n*m區域 
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};  //4個搜尋方向
//從(x,y)開始搜尋,當前數字之和cursum,當前格子數ans 
void dfs(int x,int y,int cursum,int ans)
{
	int i,temp;
	int newx,newy;
	//剪枝:若發現當前格子數超過了最優解,則不再繼續搜尋該方案 
	if(ans>minans)
		return;
	//終點:找到符合要求的解 
	if(cursum==sum/2)
	{
		//更新minans 
		if(ans<minans)
			minans=ans;
		return;
	}
	//試探上下左右4個搜尋方向 
	for(i=0;i<4;i++)
	{
		newx=x+dir[i][0];
		newy=y+dir[i][1];
		//若新位置(newx,newy)不出界並且未走過 
		if(newx>=0 && newx<n && newy>=0 && newy<m && !vis[newx][newy])
		{
			//則從新位置開始繼續搜尋 
			vis[newx][newy]=1;
			dfs(newx,newy,cursum+map[newx][newy],ans+1);
			vis[newx][newy]=0;
		}
	}
}
int main()
{
	int i,j;
	scanf("%d %d",&m,&n);
	for(i=0;i<n;i++)
	{
		for(j=0;j<m;j++)
		{
			scanf("%d",&map[i][j]);
			sum+=map[i][j];
		}
	}
	//剪枝:區域數字之和為奇數,則無解 
	if(sum%2!=0)
		printf("0\n");
	//其它情況:從左上角格子(0,0)開始搜尋 
	else
	{
		memset(vis,0,sizeof(vis));
		vis[0][0]=1;
		dfs(0,0,map[0][0],1);
		//若minans未更新,則無解 
		if(minans==INF)
			printf("0\n");
		//否則輸出最優解 
		else
			printf("%d\n",minans);
	}
	return 0;
}

相關文章