【蓝桥杯竞赛】“序列计数”&越界问题的探究

【蓝桥杯竞赛】“序列计数”&越界问题的探究

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中的错误了。
所以,保险起见,还是不要越界!

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