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<2的31次方
输出
一行若干个整数,表示和为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;
}