训练内容:
信息学奥赛一本通 分治算法
题目链接:http://ybt.ssoier.cn:8088/index.php
分治算法简单理解:
分治算法和贪心本质是一样的,就是一种代码思维,而不是一种编程的框架。本次训练基本上是用递归来实现的。
分治算法核心是:分而治之,就是将原问题划分成n个规模较小,并且和原问题相似的子问题,递归的去解决这些问题,然后将结果合并,最后得到原问题的答案。
分治算法的递归实现中,每一层递归都包含了这样三个操作:
1,分解:将原问题分解成一系列子问题。
2,解决:递归地求解各个子问题,若子问题足够小,则直接求解。
3,合并:将子问题的结果合并成原问题。
分治算法原则如下:
1,原问题和子问题使用相同的计算模式
2,子问题之间相互独立,不会相互影响
3,当子问题足够小,可以直接求解出答案
4,子问题解决后,还可以合并为原问题期望的结果,而且合并的时间复杂度要低于原问题直接解决的时间复杂度
本周训练感悟:
本次训练先了解了分治算法,做题过程中分不出最小问题的情况(比如以下黑白棋子的移动),导致做题有点慢,还有的题目是有规律的(比如以下的2011题),个人感觉对二分法的掌握也蛮熟练了,再接再厉吧!!
题目整理:
黑白棋子的移动:
【题目描述】
有2n个棋子(n≥4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如下图为n=5的情形:
○○○○○●●●●●
移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如n=5时,成为:
○●○●○●○●○●
任务:编程打印出移动过程。
【输入】
输入n。
【输出】
移动过程。
【输入样例】
7
【输出样例】
step 0:ooooooo*******–
step 1:oooooo–o
step 2:oooooo*–o*
step 3:ooooo–oo
step 4:ooooo–oo
step 5:oooo–ooo
step 6:oooo***–ooo*
step 7:ooo–oooo*
step 8:oooo**–ooo*
step 9:o–o**ooooo*
step10:ooo*–oooo
step11:–ooooooo*
题目理解:
中问题,理解肯定没问题;
题目解析:
因为棋子每次都是按规律先一半白后一半黑的,所以每次把最中间的两个也就是一黑一白移动到最后就可以,当然还要加上最后的“–”也就是把最后的“–”再移动到中间的空位就可以了。(我觉得正常人一开始都这样想)
我一开始确实是这样想的,哈哈哈但是后来发现不对。
首先我们看n=4时;
初始时:○○○○●●●●
第1步:○○○——●●●○●(—表示空位)
第2步:○○○●○●●——●
第3步:○——●○●●○○●
第4步:○●○●○●——○●
第5步:——○●○●○●○●
是不是跟我们想的不太一样呢;
但是当试n=5时;
初始时:○○○○○●●●●●
第1步:○○○○——●●●●○●
第2步:○○○○●●●●——○●
……
是不是就是我们想的那样呢 所以我们只需要特殊考虑n=4就好了。代码如下:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<string>
#define INF 999999999
#define N 1001
#define MOD 1000000007
using namespace std;
int n, cnt, space;
char a[N];
void print()
{
printf("step%2d:", cnt++);
for(int i=1; i<=2*n+2; i++)
printf("%c",a[i]);
printf("\n");
}
void move(int m)
{
a[space] = a[m];
a[space+1] = a[m+1];
a[m] = a[m+1]='-';
space = m;
print();
}
void mv(int m)
{
if(m==4)
{
move(4);
move(8);
move(2);
move(7);
move(1);
}
else
{
move(m);
move(2*m-1);
mv(m-1);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
a[i]='o';
for(int i=n+1;i<=2*n;i++)
a[i]='*';
for(int i=2*n+1;i<=2*n+2;i++)
a[i]='-';
cnt = 0;
space = 2*n+1;
print();
mv(n);
return 0;
}
2011题:
【题目描述】
已知长度最大为200位的正整数n,请求出2011n的后四位。
【输入】
第一行为一个正整数k,代表有k组数据(k≤200),接下来的k行,每行都有一个正整数n,n的位数≤200。
【输出】
每一个n的结果为一个整数占一行,若不足4位,去除高位多余的0。
【输入样例】
3
5
28
792
【输出样例】
1051
81
5521
题目理解:
老样子中问题肯定理解啦;(仪式感不能丢)
题目解析:
我一看题目n的位数……必然是int的范围不够呀,肯定要用字符的,(编程小白的蜜汁自信)但是之后呢 就蒙了,咋整呢应该(然后就开始“猜题”就是猜为啥是2011而不是别的,所以就百度了一下)发现2011^500%10000=1也就是每500就是一个循环,我们对n操作n%500再去求2011的n次方就好了,因为1000%500=0也就是最后答案只和n的后三位有关,当然n==0时答案为1;代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<cmath>
#include<map>
#include<iomanip>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int k,zs,sum;
string n;
int main()
{
cin>>k;
while(k--)
{
zs=0;
sum=1;
cin>>n;
int len=n.size();
if(len>3)
{
for(int i=len-3; i<len; i++)
zs=zs*10+(n[i]-'0');
}
else
{
for(int i=0; i<len; i++)
zs=zs*10+(n[i]-'0');
}
zs%=500;
for(int i=1;i<=zs;i++)
sum=sum*2011%10000;
cout<<sum<<endl;
}
}
Great minds have purpose, others have wishes.