感悟总结
第一道填空题和第二道填空题一上来就dfs和bfs,确实有压力。
第三题魔方状态又特别复杂,第四题,方格分割,如果没有想到从中心点两边深搜,那估计这年考试的兄弟心态都炸了。
第六题动态规划,第七题扩展欧几里得,好在后面的编程大题难度不是太高,最后两道大题纯暴力也能得不少分数!
个人感觉这年的考试题是真题里面难度最大的一年(也可能是我水平太低了)但实际上前面难,后面难度会降低一点。
本文有自己的思路,也有对网络上思路的借鉴,但不保证百分百正确,如有错误不足,希望得到您的指正!
一、迷宫(填空题)
X星球的一处迷宫游乐场建在某个小山坡上。
它是由10x10相互连通的小房间组成的。
房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立,则:L表示走到左边的房间,R表示走到右边的房间,U表示走到上坡方向的房间,D表示走到下坡方向的房间。
X星球的居民有点懒,不愿意费力思考。他们更喜欢玩运气类的游戏。这个游戏也是如此!开始的时候,直升机把100名玩家放入一个个小房间内。
玩家一定要按照地上的字母移动。
迷宫地图如下:
UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR
请你计算一下,最后,有多少玩家会走出迷宫?
而不是在里边兜圈子。
请提交该整数,表示走出迷宫的玩家数目,不要填写任何多余的内容。
如果你还没明白游戏规则,可以参看一个简化的4x4迷宫的解说图
-
就考试来说我更推荐手画,这道题手算一共用时不超过6,7分钟,编程去做的时间远远大于手算时间,技巧就是先把四边画好,将可以通过的地方画黑,不能通过的画叉,一个点如果能走出去,那么所有能到达这个点的点都能走出去。不在四边时,L在左R在右直接画叉,D在上U在下直接画叉,最后数涂黑的地方一共有31个。
-
就编程而言,这道题是明显的暴力深搜+标记
#include<bits/stdc++.h>
using namespace std;
int vis[15][15];//用来标记是否走出去
int str[15][15];//用来储存迷宫
int ans;//用来计数走出去的个数
bool check(int x,int y){
return x>=0&&x<10&&y>=0&&y<10; //判断是否走出去,没有走出去返回false,走出去返回true
}
void dfs(int x,int y)
{
if(!check(x,y))
{
ans++;
return;
}
if(vis[x][y]==0)
{
vis[x][y]=1;//表示这个位置已经走过,加标记
switch(str[x][y])
{
case 'U':return dfs(x-1,y);
case 'D':return dfs(x+1,y);
case 'L':return dfs(x,y-1);
case 'R':return dfs(x,y+1);
}
}
}
int main(){
int q=0;
string s="UDDLUULRULUURLLLRRRURRUURLDLRDRUDDDDUUUUURUDLLRRUUDURLRLDLRLULLURLLRDURDLULLRDDDUUDDUDUDLLULRDLUURRR";
for(int i=0;i<10;i++){
for(int j=0;j<10;j++)
{
str[i][j]=s[q];
q++;
}
}
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
memset(vis,0,sizeof(vis));//每次循环都要将标记数组重新清零
dfs(i,j);
}
}
cout<<ans<<endl;
return 0;
}
二、跳蚱蜢(填空题)
有9只盘子,排成1个圆圈。其中8只盘子内装着8只蚱蜢,有一个是空盘。我们把这些蚱蜢顺时针编号为 1~8每只蚱蜢都可以跳到相邻的空盘中,也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。
请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,并且保持空盘的位置不变(也就是1-8换位,2-7换位,…),至少要经过多少次跳跃?
注意:要求提交的是一个整数,请不要填写任何多余内容或说明文字。
思路:
- 怎么去表示这个圆圈?用数组,我们将空盘记为0,初始状态为012345678,目标状态为087654321。
- 怎么进行状态的演变,第一轮开始,1和8都可以跳到0位置,7和2也可以跳到空盘里。因此每一轮都可以产生四个新的状态。我们可以定义一个数组包含1,2,-1,-2四个数来表示四个方位,但是会超出数组边界,就用到(x+9)%9,用当前位置+9再对9取模,就是空盘交换的位置。
- 求最少的步数,明显要考虑广搜。实际上这道题的原理并不复杂,只是考查的内容较多,有结构体的声明,set去重和队列的应用。
#include<bits/stdc++.h>
using namespace std;
char *start="012345678";//声明两个字符类型的指针,表示初始状态和目标状态
char *target="087654321";
struct StateAndLevel{//声明结构体,状态和层次
char *state;//当前状态,即对应此时的字符串
int level; //层次 ,即走了几步
int pos0;//0的初始位置
StateAndLevel(char* state,int level,int pos0):state(state),level(level),pos0(pos0){}//构造函数
};
struct cmp{//声明结构体,用于和set进行比较
bool operator()(char *a,char *b){
return strcmp(a,b)<0;//表示比较结果不相等
}
};
//声明队列和set集合
queue<StateAndLevel>q;
set<char*,cmp>allState;//cmp是加的比较
//交换的函数
void swap(char *s,int a,int b){
char t=s[a];
s[a]=s[b];
s[b]=t;
}
void addNei(char* state,int pos,int newPos,int le){//添加邻居
char *new_state=(char *)malloc(9*sizeof(char));//为新的状态分配内存空间
strcpy(new_state,state);//将state拷贝到new-state中
swap(new_state,pos,newPos);//交换
if(allState.find(new_state)==allState.end()){//set内找不到重复元素就执行
allState.insert(new_state);//把新的状态添加到set集合中
q.push(StateAndLevel(new_state,le+1,newPos));//将新的状态放在队列的末端
}
}
int main(){
q.push(StateAndLevel(start,0,0));//初始状态,层次为0,放到队列中
while(!q.empty()){//如果队列不为空,就执行循环
StateAndLevel sal=q.front();//访问队列头结点
char *state=sal.state;//声明状态
int le=sal.level;//声明层次
int pos0=sal.pos0;//声明0的位置
allState.insert(state); //添加到set避免重复
if(strcmp(state,target)==0){//退出循环的出口,状态和目标状态相同执行
printf("%d",le);
return 0;
}
int new_pos=(pos0-1+9)%9;//空盘与右边第一个盘子交换
addNei(state,pos0,new_pos,le);
new_pos=(pos0+1+9)%9;//空盘与左第一个盘子交换
addNei(state,pos0,new_pos,le);
new_pos=(pos0-2+9)%9;//空盘右二交换
addNei(state,pos0,new_pos,le);
new_pos=(pos0+2+9)%9;//空盘左二交换
addNei(state,pos0,new_pos,le);
q.pop();//弹出队列第一个元素
}
return 0;
}
三、魔方状态(填空题)
二阶魔方就是只有2层的魔方,只由8个小块组成。如下所示:
小明很淘气,他只喜欢3种颜色,所有把家里的二阶魔方重新涂了颜色,如下:
前面:橙色
右面:绿色
上面:黄色
左面:绿色
下面:橙色
后面:黄色
请你计算一下,这样的魔方被打乱后,一共有多少种不同的状态。
如果两个状态经过魔方的整体旋转后,各个面的颜色都一致,则认为是同一状态。
请提交表示状态数的整数,不要填写任何多余内容或说明文字。
过一段时间会补上
在这里插入代码片
四、方格分割(填空题)
6x6的方格,沿着格子的边线剪开成两部分,要求这两部分的形状完全相同。如图:p1.png, p2.png, p3.png就是可行的分割法。
试计算:包括这2种分法在内,一共有多少种不同的分割方法。
注意:旋转对称的属于同一种分割法。请提交该整数,不要填写任何多余的内容或说明文字。
技巧就是从中心对称点开始深搜,同时向两边深搜
#include<bits/stdc++.h>
using namespace std;
int ans;
int dire[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int vis[7][7];
void dfs(int x,int y){
//递归出口
if(x==0||y==0||x==6||y==6){
ans++;
return;
}
//当前的点标注为已访问
vis[x][y]=1;
//对称点也标注为已访问
vis[6-x][6-y]=1;
for(int k=0;k<4;k++){
int xx=x+dire[k][0];
int yy=y+dire[k][1];
if(xx<0||xx>6||yy<0||yy>6)
continue;
if(!vis[xx][yy]){
dfs(xx,yy);
}
}
vis[x][y]=0;
vis[6-x][6-y]=0;
}
int main(){
dfs(3,3);
cout<<ans/4<<endl;
return 0;
}//509
五、字母组串(代码补充)
由 A,B,C 这3个字母就可以组成许多串。比如:“A”,“AB”,“ABC”,“ABA”,“AACBB” …
现在,小明正在思考一个问题:如果每个字母的个数有限定,能组成多少个已知长度的串呢?
他请好朋友来帮忙,很快得到了代码,解决方案超级简单,然而最重要的部分却语焉不详。请仔细分析源码,填写划线部分缺少的内容。
对于下面的测试数据,小明口算的结果应该是:6,19。
#include<bits/stdc++.h>
using namespace std;
// a个A,b个B,c个C 字母,能组成多少个不同的长度为n的串。
int f(int a, int b, int c, int n)
{
if(a<0 || b<0 || c<0) return 0;
if(n==0) return 1;
return f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1); // 代码补充
}
int main()
{
printf("%d\n", f(1,1,1,2));
printf("%d\n", f(1,2,3,3));
return 0;
}
由于递归的出口判断条件是a小于0,或者b小于0,或者c小于0,或者n等于0,那么在填空处,a,b,c,n都在减少,代码的思路就是每抽出一个字母,串的长度减去1,直到抽出与n相等的为止,返回1代表一种可能。
填空处的答案为:f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1);
六、最大公共子串(代码补充)
最大公共子串长度问题就是:求两个串的所有子串中能够匹配上的最大长度是多少。
比如:“abcdkkk” 和 “baabcdadabc”,可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。
下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。
请分析该解法的思路,并补全划线部分缺失的代码。
#include <stdio.h>
#include<string.h>
#define N 256
int f(const char *s1,const char* s2){
int a[N][N];
int len1=strlen(s1);
int len2=strlen(s2);
int i,j;
memset(a,0,sizeof(int)*N*N);
int max=0;
for(i=1;i<=len1;i++){
for(j=1; j<=len2; j++){
if(s1[i-1]==s2[j-1]) {
a[i][j] = ___a[i-1][j-1]+1__; //代码补充
if(a[i][j] > max) max = a[i][j];
}
}
}
return max;
}
int main(){
printf("%d\n",ff("abcdkkk", "acbdchd"));
return 0;
}
七、正则问题
考虑一种简单的正则表达式:只由 x ( ) | 组成的正则表达式。
小明想求出这个正则表达式能接受的最长字符串的长度。
例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6。
输入:一个由x()|组成的正则表达式。输入长度不超过100,保证合法。
输出:这个正则表达式能接受的最长字符串的长度。
例如,输入:((xx|xxx)x|(x|xx))xx ,程序应该输出:6
#include<bits/stdc++.h>
using namespace std;
string str;
int pos,len;
int f(){
int num,tmp;
while(pos<len){
if(str[pos]=='('){//如果我们遇到(就进入一个新的函数,并用新的返回值计算
pos++;
num+=f();//等待后面的结果,并累加到num中
}
else if(str[pos]=='x'){//表示遇到的是‘x ’
pos++;
num++;
}
else if(str[pos]=='|'){
pos++;
tmp=max(num,tmp);
num=0;
}
else if(str[pos]==')'){//如果遇到)就退出当前函数
pos++;
break;
}
}
tmp=max(num,tmp);//保证最后一段也有结果
return tmp;
}
int main(){
cin>>str;
len=str.length();
int ans;
ans=f();
cout<<ans<<endl;
return 0;
}
八、包子凑数
小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入:第一行包含一个整数N。(1 <= N <= 100)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100)
输出:一个整数代表答案。如果凑不出的数目有无限多个,输出INF。
例如,输入:
2
4
5
程序应该输出:
6
再例如,输入:
2
4
6
程序应该输出:
INF
样例解释:
对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
对于样例2,所有奇数都凑不出来,所以有无限多个。
实际上这就是对2013年第八道题买不到的数目的拓展。
我们已经知道买不到的情况就是输入的种类数目不是互质数(最大公约数不为1)。通过两层for循环,求最大公约数的模板求出所有的数是不是两两互质的,如果最大公约数不是1,则输出INF!否则通过完全背包的思路
#include<bits/stdc++.h>
using namespace std;
bool f[10000];
int gcd(int a,int b)
{return b==0?a:gcd(b,a%b);}
int main(){
int n,judge=1,a[101],count=0;
f[0]=true;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
if(gcd(a[i],a[j])!=1){
printf("INF\n");
judge=0;
break;
}
}
}
if(judge==1){
for(int i=1;i<=n;i++){
for(int j=0;j<10000;j++){
if(f[j]){
f[j+a[i]]=true;//完全背包
}
}
}
for(int i=1;i<10000;i++){
if(f[i]==0){
count++;
}
}
printf("%d",count);}
return 0;
}
九、分巧克力
儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:
- 形状是正方形,边长是整数
- 大小相同
例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小Hi计算出最大的边长是多少么?
输入:第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
输入保证每位小朋友至少能获得一块1x1的巧克力。
输出:输出切出的正方形巧克力最大可能的边长。
样例输入:
2 10
6 5
5 6
样例输出:
2
用二分法优化暴力枚举
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k;
int h[100000];
int w[100000];
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>h[i]>>w[i];
}
int r=100001;//右边界
int l=1;//左边界
int ans=0;
while(l<=r)
{
int mid=(l+r)/2;
int cnt=0;
for(int i=0;i<n;i++)
cnt+=(h[i]/mid)*(w[i]/mid);
if(cnt>=k)
{
l=mid+1;//左边界加1
ans=mid;
}
else
r=mid-1;
}
cout<<ans<<endl;
return 0;
}
十、油漆面积
X星球的一批考古机器人正在一片废墟上考古。该区域的地面坚硬如石、平整如镜。管理人员为方便,建立了标准的直角坐标系。
每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。
矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。
为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。
其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
注意,各个矩形间可能重叠。
本题的输入为若干矩形,要求输出其覆盖的总面积。
输入格式:
第一行,一个整数n,表示有多少个矩形(1<=n<10000)
接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。
(0<= x1,y1,x2,y2 <=10000)
输出格式:
一行一个整数,表示矩形覆盖的总面积。
例如,
输入:
3
1 5 10 10
3 1 20 20
2 7 15 17
程序应该输出:
340
再例如,
输入:
3
5 2 10 6
2 7 12 10
8 1 15 15
程序应该输出:
128
暴力法
#include<bits/stdc++.h>
using namespace std;
bool a[10005][10005];//用int会超出题目的范围
void judge(int x1,int y1,int x2,int y2){
for(int i=x1;i<x2;i++){
for(int j=y1;j<y2;j++){
a[i][j]=1;
}
}
}
int main(){
int n,sum;
scanf("%d",&n);
for(int i=0;i<n;i++){
int x1,y1,x2,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
judge(x1,y1,x2,y2);
}
for(int i=0;i<10005;i++){
for(int j=0;j<10005;j++){
if(a[i][j]==1)
sum++;
}
}
printf("%d\n",sum);
return 0;
}
线段树+扫描线
这个解法过段时间会补上
在这里插入代码片