2021.9.14 a.m.小结

本文详细介绍了五个经典算法问题的解决方案,包括汉诺塔问题的递归求解、多项式求值的快速幂运算、求最大连续和的动态规划、寻找和为n的连续子序列的搜索策略以及螺旋矩阵中特定位置数值的计算方法。每个问题都提供了思路解析和C++代码实现,展示了不同算法在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

T1:问题 A: 汉诺塔问题

题目描述

    如图所示,设有n个大小不等的中空圆盘,按照从小到大的顺序叠套在立柱A上,另有两根立柱B和C。现要求把全部圆盘从A柱(称为源柱)移到C柱(称为目标柱),移动过程中可借助B柱(称为中间柱)。移动时有如下的要求:

      1)一次只许移动一个盘。

      2)任何时候、任何柱子上不允许把大盘放在小盘上边。

      3)可使用任意一根立柱暂存圆盘。

问:如何用最少步数实现n个盘子的移动?请打印出具体移动方案。

汉诺塔示意图

输入

一行一个正整数n,1≤n≤18。

输出

输出若干行,第i行表示第i步的移动方案。具体格式参见输出样例。

样例输入

3

样例输出

A->C

A->B

C->B

A->C

B->A

B->C

A->C

题解

作为经典的dfs题,现在来看看整个游戏的过程是什么。首先,要移走最下面的盘子,必须要把上面所有盘子从A盘移到B盘,把最后一个盘子从A盘移到C盘,再把其他盘子从B盘移到C盘。因此如果能够知道上面(k-1)个盘子怎么移,就可以完成k个盘子。如何移(k-1)个盘子?先把第(k-1)个盘子上面的所有盘子移到转移盘中,把(k-1)盘移到C……重复上述过程,就是一个递归。递归边界是什么?只有一个盘子怎么移?是不是就直接从当前盘移到C盘。因此直接用一个dfs函数就可以了。

参考代码

#include<cstdio>
using namespace std;
int n;
void dfs(int k,char a,char b,char c)
{
	if(k==1)
	{
		printf("%c->%c\n",a,c);
		return;
	 } 
	dfs(k-1,a,c,b);
	printf("%c->%c\n",a,c);
	dfs(k-1,b,a,c);
}
int main()
{
	scanf("%d",&n);
	dfs(n,'A','B','C');
	return 0;
}

T2:问题 B: 多项式求值

题目描述

输入x和系数a0,a1,a2,...,an,计算的值

输入

第一行两个整数,表示n和x的值,1<=n<=10,-5<=x<=5

第二行n+1个整数,表示a0,a1,a2,...,an的值,每两个整数之间有一个空格,-10<=ai<=10

输出

一行一个整数,表示多项式的结果

样例输入

3 2

1 2 3 4

样例输出

49

题解

用一个函数计算次方,这题就完了(比较懒,直接写在主函数里)。直接顺推,上代码。

参考代码

#include<cstdio>
#define db long long
using namespace std;
int n;db x,a[30],ans=0;
int main()
{
	scanf("%d%lld",&n,&x);
	for(int i=0;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		db ret=1ll;
		for(int j=1;j<=i;j++)
		{
			ret*=x;
		}
		ret*=a[i];
		ans+=ret;
	}
	printf("%lld",ans);
	return 0;
}

T3:问题 C: 最大连续和

题目描述

在一个整数序列中,找出连续若干个整数的和的最大值

输入

第一行,一个整数n,表示整数的个数 (0<n<=10000)

第二行,n个整数  整数取值范围为(-10000,10000)

输出

一个整数,表示最大的连续和

样例输入

6

1 -1 2 3 -7 9

样例输出

9

题解

这道题之前遇到过,卡过数据,需要快读快写和单纯两个变量s1和s2来解决。这里明显简单的多,只需要用二维dp(滚动数组都不用),dp[i][0]表示前k个数中不选第k个数所得最大值,dp[i][1]同理,只是要取第k个数。转移也很简单,之和i-1的dp有关,代码中有体现。

参考代码

#include<cstdio>
using namespace std;
int dp[10010][2],n,a[10010];
int max1(int p,int q) { return p>q?p:q; }
int main()
{
	scanf("%d",&n);
	dp[0][0]=dp[0][1]=-2110000000;//初值赋为极小值
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&a[i]);
		dp[i][0]=dp[i][1]=-2110000000;//同上
	}
	for(int i=1;i<=n;i++)
	{
		dp[i][0]=max1(dp[i-1][0],dp[i-1][1]);
		dp[i][1]=max1(dp[i-1][1]+a[i],a[i]);
	}
	printf("%d",max1(dp[n][0],dp[n][1]));
	return 0;
}

T4:问题 D: 和为n的子序列

题目描述

 输入n,从1,2,3,....,n的数列中,找出最前面的总和为n的连续子数列

输入

一行一个整数n,   0<n<231次方

输出

一行若干个整数,表示和为n的连续子序列,每两个数之间有一个空格

样例输入

5

样例输出

2 3

题解

关于这道题,我没用正解就过了(数据水吗,不咋个水,只是考虑的足够多,就可以了)。此处就讲讲我的方法(毕竟我也没打正解):类似于随机算法,但是每次都能保证能完全取到答案。对于一串连续的数,我们关心的只有左端点和右端点,因此只要找到合适(随机点呗)的左右端点,并且保证这是第一个枚举的,那就是答案了。如何保证是第一个?左端点最小就OK了呗,因此左端点初值为1,右端点初值为n,然后二分找到第一个合适的右端点,即这个区间的数相加的和恰好小于等于n。如果不行,就增加左端点l。由于数的性质,一个大的合数的第一个区间的左端点不会太大(蒙的),因此可以在规定范围内搞定答案。现在来看质数。质数没啥好说的,只能n/2和n/2+1,因此本题开始时还要筛质数筛到10^6就够了(开根号)。总之,这不是正解,而是一种玄学做法,以上很多东西我都无法给出证明,但是这是对的...(待续)

参考代码

#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
long long n,l,r;

template <typename T>inline void prt(T x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) prt(x / 10);
	putchar(x % 10 + '0');
}
long long mf[150010],prime[150010],cnt=0,ans=0;

int main()
{
	for(int i=2;i<=150000;i++)
	{
		if(!mf[i])
		{
			mf[i]=i;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt&&prime[j]*i<=150000;j++)
		{
			mf[prime[j]*i]=prime[j];
			if(prime[j]==mf[i]) break;
		}
	}
	scanf("%lld",&n);
	int pd=0;
	for(int i=1;i<=cnt;i++)
	{
		if(prime[i]*prime[i]>n) break;
		if(n%prime[i]==0)
		{
			pd=1;break;}
	}
	if(pd==0) 
	{
		printf("%d %d",n/2,n/2+1);
		return 0;
	}
	l=1;r=n;
	for(;;)
	{
//		ans++;
//		if(ans>=7000000)
//		{
//			printf("%d %d",n/2,n/2+1);
//			return 0;
//		}
		long long l1=l,r1=r*2;
		while(l1<r1)
		{
			long long mid=(l1+r1)/2;
			if((l+mid)*(mid-l+1)/2ll>=n) r1=mid;
			else l1=mid+1;
		}
		r=r1;
		if((l+r)*(r-l+1)/2==n)
		{
			for(long long i=l;i<=r;i++) 
			{
				prt(i);
				printf(" ");
			}
			return 0;
		}
		while((l+r)*(r-l+1)/2>n&&l<=r) l++;
//		l1=l,r1=r;
//		while(l1<r1)
//		{
//			long long mid=(l1+r1)/2;
//			if((l+mid)*(mid-l+1)/2ll>=n) r1=mid;
//			else l1=mid+1;
//		}
//		l=r1;
		if((l+r)*(r-l+1)/2==n)
		{
			for(long long i=l;i<=r;i++)
			{
				prt(i);
				printf(" ");
			}
			return 0;
		}
	}
	printf("%lld",n);
	return 0;
}

T5:问题 E: 螺旋矩阵

题目描述

    一个n行n列的螺旋矩阵可按如下方法生成:从矩阵的左上角(第1行第1列)出发,初始时向右移动;如果前方是未曾经过的格子,则继续前进;否则右转。重复上述操作,直至经过矩阵中的所有格子。根据经过顺序,在格子中依次填人1,2,3, ...n2,便构成了一个螺旋矩阵。如图是一个n=4时的螺旋矩阵。

    现给出矩阵大小n以及i和j,请求出该矩阵中第i行第j列的数是多少。

螺旋矩阵

输入

    1行3个整数n、i和j,每两个整数之间用一个空格隔开,分别表示矩阵大小、待求的数所在的行号和列号。

输出

    一行一个整数,表示相应矩阵中第i行第j列的数。

样例输入

4 2 3

样例输出

14

提示


【数据规模】


对于50%的数据满足:1≤n≤100。


对于100%的数据满足:1≤n≤30000,1≤i≤n,1≤j≤n。

题解

这道题最开始使用模拟做的。只是当数据大于10000时就会超时,因此考虑用数学方法。如果我们把这个矩形由外而内划分成很多个“圈”,那么就能O(1)判断某一个数在哪一个圈上。这个圈之前的所有个数之和也是可以O(1)解决的。现在只需要模拟一圈,也就是根号n的效率就能完成本题。那么如何判断在第几圈上?其实很简单,只用求出这个点靠哪一边最近,就在第几圈。这样就能在规定效率内完成本题。

参考代码

#include<cstdio>
using namespace std;
int min1(int p,int q) { return p<q?p:q; }
int abs1(int p) { return p>0?p:-p; }
int dx[5]={0,0,1,0,-1};
int dy[5]={0,1,0,-1,0};
int n,i,j,ans=0;
int main()
{
	scanf("%d%d%d",&n,&i,&j);
	int pos=min1(min1(i,n-i+1),min1(j,n-j+1));
	ans+=(n-1+n-1-2*(pos-2))*(pos-1)*2;
	int i1=pos,j1=pos;ans++;
	if(i==pos&&j==pos) 
	{
		printf("%d",ans);
		return 0;
	}
	for(int i2=1;i2<=4;i2++)
	{
		for(int j2=1;j2<=n-1-2*(pos-1);j2++)
		{
			i1+=dx[i2];j1+=dy[i2];
			ans++;
			if(i1==i&&j1==j)
			{
				printf("%d",ans);
				return 0;
			}
		}	
	}
}

T6:问题 F: 第 k 小整数

题目描述

现有 n 个正整数,n≤10000,要求出这 n 个正整数中的第 k 个最小整数(相同大小的整数只计算一次),k≤1000,正整数均小于 30000。

输入

第一行为 n 和 k,第二行开始为 n 个正整数的值,整数间用空格隔开。

输出

第 k 个最小整数的值;若无解,则输出“NO RESULT”。

样例输入

10 3

1 3 3 7 2 5 1 2 4 6

样例输出

3

题解

这道题的标答是用快排O(n)去做。我不会……就用的树状数组。配合一种桶的思想,每次树状数组实时更新比这个数小的有多少,然后等所有数都更新完了,就从1扫到30000,看第几个数是“第K大的数”,如果不存在,即可能“总数没有k个”或“排不到k,可能是1,2,3,3,5等”,就按要求输出即可。

参考代码

#include<cstdio>
using namespace std;
int n,k,a[40010],c[40010];
void add(int t,int s)
{
	for(;t<=30000;t+=t&-t) c[t]+=1;
}
int query(int t)
{
	int ret=0;
	for(;t;t-=t&-t) ret+=c[t];
	return ret;
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		int p;
		scanf("%d",&p);
		if(a[p]) continue; 
		a[p]=1;add(p,1);
	}
	for(int i=1;i<=30000;i++)
	{
		if(query(i)==k&&a[i]==1)
		{
			printf("%d",i);
			return 0;
		}
	}
	printf("NO RESULT");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值