这是我第一天使用模板,若有不妥之处请指出!
前言
本文原题目为2016年“梦想杯”年江苏省信息与未来小学生程序设计比赛第三题。
一、题目描述
小明把n(n为偶数)张牌按编号顺序1,2,3,...,n排成一堆,然后开始洗牌。一次洗牌的过程如下:
1 对于一堆牌编号为a1,a2,...,an,首先把牌分成均匀的两堆:a1,a2,..am,am+1,am+2,...an(其中m=n/2)
2然后按顺序交叉插入:a1,am+1,a2,am+2,...am,an
洗牌过程共重复了k次,请你标程帮助小明模拟洗牌的过程。牌堆的初始顺序是a1,a2,...,an。例如n=6,则牌堆的初始顺序是1,2,3,4,5,6。
首次洗牌时,会把牌分成1,2,3和4,5,6两堆,交叉插入后的结果为1,4,2,5,3,6。
第二次洗牌时,会把牌分成1,4,2和5,3,6两堆。交叉插入后得到1,5,4,3,2,6.
输入
三个整数n,k,i,分别表示牌的数量,洗牌的次数,牌的位置。
输出
n张牌洗过k次后,牌堆中第i张牌的编号。
样例组
样例1
输入 6 2 5
输出 2
样例2
输入 400 300 200
输出 368
数据范围
对于100%的数据,1<=n,k<=1000,1<=i<=n,题目保证n是偶数。
二、题目解决
从题目描述中可以发现,这道题目是一道模拟题,而且还是比较简单的那种。这道题目需要用数组模拟牌堆,将数组中从1到n的数赋初值(即这个数的位置),再用for循环模拟(模拟用的都是循环)k次洗牌过程,最后直接输出第i位上的数就完事。
赋初值时的程序十分简单(而且都会写),这里就一笔带过了。
//设排队数组为p
for(int i=1;i<=n;i++) p[i]=i;
这道题目的主要难点在于模拟。这道题目的模拟过程说的很清楚,用双数组就可以了。所以这样的程序不花多久就能写出来(具体思路见题目描述)。
void cx(int n,int k,int &a[])
{
int b[100001];
for(int j=1;j<=k;j++)
{
memset(b,0,sizeof(b));
for(int i=1;i<=n/2;i++) b[i*2-1]=p[i];
for(int i=n/2+1;i<=n;i++) b[(i-n/2)*2]=p[i];
for(int i=1;i<=n;i++) p[i]=b[i];
//for(int i=1;i<=n;i++) cout<<p[i]<<' ';
//cout<<endl;
}
}
这样写是能在某些OJ上过的,但一把数据范围开大点就会时间超限。所以这段模拟程序需要优化。 我们可以以样例1为例(n=6),对其进行手工模拟(也可以直接用计算机直接输出,把注释符号去掉就行了)。
模拟的表格如下(n=6):
洗牌次数 | 牌堆 | |||||
1 | 1 | 5 | 4 | 3 | 2 | 6 |
2 | 1 | 3 | 2 | 5 | 4 | 6 |
3 | 1 | 2 | 3 | 4 | 5 | 6 |
4 | 1 | 4 | 2 | 5 | 3 | 6 |
再往后,可以发现,这些都是循环出现的。同时也可以发现,不管是什么数,第一位和最后一位永远不变,所以一个循环节共有n-2个个体(也就是牌洗完后的队列)。那么优化后的模拟过程是这样的:
//这里改用a作为洗牌数组
void cx(int n,int k,int a[])
{
int b[100001];
for(int j=1;j<=k%(n-2);j++)
{
memset(b,0,sizeof(b));
for(int i=1;i<=n/2;i++) b[i*2-1]=a[i];
for(int i=n/2+1;i<=n;i++) b[(i-n/2)*2]=a[i];
for(int i=1;i<=n;i++) a[i]=b[i];
}
}
具体标程
具体标程如下:
#include<bits/stdc++.h>
#define maxn 100001
using namespace std;
int n,m,o,a[maxn],b[maxn];
int main()
{
cin>>n>>k>>m;
for(int i=1;i<=n;i++) a[i]=i;
for(int j=1;j<=k%(n-2);j++)
{
memset(b,0,sizeof(b));
for(int i=1;i<=n/2;i++) b[i*2-1]=a[i];
for(int i=n/2+1;i<=n;i++) b[(i-n/2)*2]=a[i];
for(int i=1;i<=n;i++) a[i]=b[i];
}
cout<<a[m];
return 0;
}
这道题目就这么多。