【藍橋杯競賽】“序列計數”&越界問題的探究

【藍橋杯競賽】“序列計數”&越界問題的探究

03-14模擬賽賽題第九題“序列計數”鏈接

1.問題描述與思路

【問題描述】
小明想知道,滿足以下條件的正整數序列的數量:

1. 第一項爲 n; 2. 第二項不超過 n;3. 從第三項開始,每一項小於前兩項的差的絕對值。
請計算,對於給定的 n,有多少種滿足條件的序列。
【輸入格式】 輸入一行包含一個整數 n。
【輸出格式】 輸出一個整數,表示答案。答案可能很大,請輸出答案除以10000的餘數。
【樣例輸入】 4
【樣例輸出】 7
【樣例說明】
以下是滿足條件的序列: ①4 1 ②4 1 1 ③4 1 2 ④4 2 ⑤4 2 1 ⑥4 3 ⑦4 4
【評測用例規模與約定】 對於 20%的評測用例,1 <= n <= 5; 對於 50% 的評測用例,1 <= n <= 10; 對於 80% 的評測用例,1 <= n <=100; 對於所有評測用例,1 <= n <= 1000。

【思路】 審題後不難發現此題有遞歸的味兒,可以先在紙上列出幾個實例,發現不同實例有重複的計算部分,再看到"答案可能很大,請輸出答案除以10000的餘數",因此這題很可能得用DP(動態規劃)+記憶化搜索,關鍵在於寫出狀態轉移方程,在此之前要找準特徵。
【歷程】 暴力遞歸–>記憶化搜索O(N3N^3)–>優化後的記憶化搜索O(N2N^2)

2.題解

#include<iostream>
#include<ctime>
#include<cmath>
#define loop(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
typedef long long LL;
//int mem[1001][1000]; //第二維 爲何可以是1000 ,如果輸入1000不就越界了?
int mem[1001][1001]; //保險起見還是不要用上面的
const int MOD=10000;

// f(pre,cur)=1+f(cur,1)+f(cur,2)+...+f(cur,abs(pre-cur)-1)
LL dfs(int pre,int cur){
	if(mem[pre][cur]!=0)
		return mem[pre][cur];
	LL ans=1;
	loop(i,1,abs(pre-cur)-1)
		ans=(ans+dfs(cur,i))%MOD;
		
	return mem[pre][cur]=ans;//一定要記錄狀態,否則與暴力遞歸無異 
}
//優化 
//f(pre,cur)=1+f(pre,cur-1)+f(cur,abs(pre-cur)-1)
//其中 cur爲1-->cur,包括了一層循環 降爲O(N^2) 
LL dfs_plus(int pre,int cur){
	if(cur<=0)
		return 0;
	if(mem[pre][cur]!=0) return mem[pre][cur];
	return mem[pre][cur]=(1+dfs_plus(pre,cur-1)+
	dfs_plus(cur,abs(pre-cur)-1))%MOD;
}

//O(N^3) 
/*
int main(){
	int n;
	LL ans=0;
	cin>>n;
	LL a=clock();
	loop(i,1,n) ans=(ans+dfs(n,i))%MOD;
	cout<<ans<<endl;
	cout<<"runtime:"<<(clock()-a)<<"ms"<<endl;
	return 0;
}
*/
//O(N^2) 
int main(){
	int n;	
	cin>>n;
	LL a=clock();	
	cout<<dfs_plus(n,n)<<endl;
	//cout<<mem[1000][1000]<<endl;  //C++二維數組,有計算偏移機制 
	cout<<"runtime:"<<(clock()-a)<<"ms"<<endl;
	return 0;		
}

3.過程中遇到的問題&探索

官方解答中數組mem爲1001*1000的二維數組,而主函數中的訪問操作爲mem[1000][1000],按常理來說第二維已經訪問越界了,那麼爲什麼結果沒啥問題呢?
將上述問題一般化,即對於二維數組array[N+1][M],訪問array[N][M]是否越界?
ans:結果來看好像沒有,但是我覺得嚴格來說應該越界了(從數組的意義來說)。多維數組的物理存儲是一維的,該二維數組元素總數爲:(N+1)*M,array[x][y]相當於array[x*M+y],因此array[N][M]爲數組中第(N*M+M)個元素(最後一個元素),它在最末元素array[N][M-1]的後一個,已經越界。之所以array[N][M]仍可以存儲值,我想我已經從幾個實例中找到了原因:
實例1

int main(){
	int a[5][5]={0};
	a[-1][-2]=5;
	a[10][10]=100;
	cout<<&a[-1][-2]<<","<<&*(*(a-1)-2)<<","<<a[-1][-2]<<endl;
	cout<<&a[10][10]<<","<<&*(*(a+10)+10)<<","<<a[10][10]<<endl;	
	return 0;
}

運行結果
實例1
由此可見,其實二維數組的下標是相對於其首地址的偏移,越界的地方是未知的——有可能是塊“空地”,也有可能已經被佔用,只不過在二維數組開闢地的附近大概率(或是碰巧)沒被佔用而已。爲了進一步證明,我們故意設置些陷阱,如實例2所示:

int main(){
	int a[5][5]={0};
	int b=777;
	int* ptr=&b;
	int p1=(int)ptr;//把地址數值化 
	int p2=(int)a;
	cout<<a<<","<<p2<<endl;
	cout<<ptr<<","<<p1<<endl;	
	//從a的首地址偏移到變量b的地址處並修改值 
	cout<<a[0]+(p1-p2)/sizeof(int)<<","<<&b<<endl;	
	*(a[0]+(p1-p2)/sizeof(int))=4396;
	cout<<b<<endl;//b從777 變爲了 4396 
	return 0;
}

運行結果
實例2
從上述代碼可見,我們進行越界的偏移很可能就使得某一處的變量“躺槍”了,即可能會發生一些我們不想遇到的事情
況且競賽題中mem[1000][1000]的地址爲&mem+1,即指數組mem後的第一個元素位置,一般來說都是安全地帶(畢竟選址的位置對應的空間要足夠裝下這個數組嘛),因此結果纔沒有問題,但是對於那種更爲偏遠的地帶,那就很可能出現實例2中的錯誤了。
所以,保險起見,還是不要越界!

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