题目序:
1.[USACO1.5]八皇后 Checker Challenge - 洛谷
4.[COCI2008-2009#2] PERKET - 洛谷
题目:
我一开始想先从第一行看起,利用一个循环试探能在这一行的哪个位置上下子,我的想法是从一号位置到n号位置试探,当下了一个棋后利用循环给它的横行、竖列、正对角线、反对角线上的所有元素全部标记一遍,标记后直接递归到下一行重新考虑,但是这样会出现错误的“标记“操作或者错误的“取消标记”操作,导致无法得到答案,中途又由于思路错误浪费了好几个小时,(而我自己却一直觉得没有问题),最后参考博客明白过来“可以用数组表示一类目标“,利用规律”自右上方到左下方的对角线上的任意坐标满足‘x+y’=‘定值’“”“自左上方到右下方的对角线上的任意一点的坐标满足‘x-y’=‘定值’”,可以用数组下标表示一整个对角线,这样就可以轻易的完成”标记“这个任务了。
#include<stdio.h>
#include<stdlib.h>
int n,a[30],b[30],c[30],d[30];//a,b,c,d四个数组分别表示竖列,横行,正对角线,反对角线,n表示矩阵大小
int k1=0,sum=0;//sum表示满足题意的解的和,k1限制输出解的次数为三次
void dfs(int k)
{
int i;
//printf("a\n");
//输出
if(k>n)
{
if(k1<3)
{
for(i=1;i<=n;i++)
printf("%d ",b[i]);
printf("\n");
k1++;
}
sum++;
return ;
}
//下棋并标记
for(i=1;i<=n;i++)
{
if(a[i] == 0 && c[i+k] == 0 && d[i-k+n] == 0)//当该行中的这个点所在竖列和两条对角线上的元素都为0时
{
a[i]=1;//则将该点赋为1,表示将一个皇后棋下在这里
b[k]=i;//同时记录行数
c[i+k]=1;//假设所有皇后棋全放在第一竖列,,第二竖列。。。可观察到此时空余空间中的正反对角线与皇后棋位置之间的规律
d[i-k+n]=1; //即正对角线在该行中为“皇后位置+行数”,反对角线在该行中为“皇后位置-行数”,即(i-k)
//但是(i-k)可能为负数,则加上一个“总行数n”
dfs(k+1);//向下一行进行尝试
a[i]=0;
b[k]=0;
c[i+k]=0;
d[i-k+n]=0;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1);
printf("%d",sum);
getchar();
return 0;
}
题目:
由题目意思可知,该题是要求我们将同一科目的所有“完成习题册”的时间尽可能均衡地分配给左右脑(双核就是强),然后选取各个科目的耗时较多的部分,相加就是正确答案。
说起来很简单,就像一道简单的贪心类水题,但实际上需要用到动态规划,主要是解法类似于动态规划里的经典例题“01背包”。(而动态规划0基础的我足足花了一整个上午才勉强明白)
关于动态规划的学习笔记我之前的博客里已经发过,这里就不再重复展示了
回到题目。既然按题意来说要使左右脑所分配的时间尽可能相近,那么在这题里就可以把总时间的1/2当作背包总容量,随后利用循环逐个判断“该物品能不能装进背包”,“装进这个物品得到的收益大还是不装大”,最后得到的数值一定是最接近总时间的1/2同时又小于总时间的1/2的时间
#include<stdio.h>
int s[4],z[4],dp[2000],a[100];
int sum;
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int i,j,k;
for(i=0;i<4;i++)
scanf("%d",&s[i]);//s[i]表示第i门科目的习题集个数
for(i=0;i<4;i++)
{
for(j=0;j<s[i];j++)
{
scanf("%d",&a[j]);
z[i]+=a[j];
}
for(j=0;j<s[i];j++)
{
for(k=z[i]/2;k>=0;k--)
{
//这里只需要考虑可以取的情况,因为若是无法取到,可取物品数量-1,而在循环中k--就直接表示“可取物品-1”
if(k>=a[j])//当 k>=a[j]时,即背包容量大于当前选中物品的体积,那么需要判断取与不取两种情况,谁的可能收益最大
{
dp[k]=max(dp[k],dp[k-a[j]]+a[j]);
}
}
}
//循环结束后,此时dp内存储着最接近z[i]/2同时小于z[i]/2的物品,那么取左右脑中较大的数,即:“z[i]-dp[z[i]/2]”
sum+=z[i]-dp[z[i]/2];
for(j=0;j<z[i];j++)
dp[j]=0;
}
printf("%d",sum);
return 0;
}
题目:
刚看到这道题我还以为和“迷宫 - 洛谷”那道题一样,然后才发现,,,居然是按象棋里面的“马”的行进方式走(不会下象棋的我表示一脸懵)
了解了一下“马”的行进方式,可以写出行进方向数组,如下图:
明白了题意后就很容易可以想到,在这里用bfs无疑是上好的方案
所以我们先定义一个结构体,包含三个成员变量,分别是横坐标,纵坐标以及当前步数
这里需要注意一下,因为在bfs中,需要用结构体数组记录某一点的所有扩展点(从某个固定的点向各个方向可以得到的点),因此为了保证不会数据溢出,需要把这个结构体数组开大些。
然后就是很常规的操作了:先将起始点压入队列并标记为“已走”,再循环遍历已知点的扩展点,记录到达每个点要用到步数,按要求输出就好了
#include<stdio.h>
int next[8][2]={{2,1},{2,-1},{-2,1},{-2,-1},{1,2},{-1,2},{1,-2},{-1,-2}};//上:右、左//下: 右、左//右:上、下//左: 上,下
int n,m,starx,stary;
int a[500][500];//存放到达该点的步数
struct abc
{
int x;
int y;
int step;//当前步数
}que[160010];//扩展点数不会超过400*400
int main()
{
int head=1,tail=1;
scanf("%d %d %d %d",&n,&m,&starx,&stary);
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
a[i][j]=-1;//先将所有点标为“未到”
a[starx][stary]=0;//将起始点标为0
que[tail].x=starx;//将起始点压入队列
que[tail].y=stary;
que[tail].step=0;
tail++;
while(head < tail)
{
int tx,ty;
for(i=0;i<8;i++)
{
tx=que[head].x+next[i][0];
ty=que[head].y+next[i][1];
if(tx < 1 || tx > n || ty < 1 || ty > m )
continue;
que[tail].x=tx;
que[tail].y=ty;
que[tail].step=que[head].step+1;
if(a[tx][ty] != -1)
continue;
a[tx][ty]=que[tail].step;
tail++;
}
head++;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
printf("%-5d",a[i][j]);
if(i != n)
printf("\n");
}
return 0;
}
题目:
这题也就是一道很普通的dfs题罢了,为了方便,我定义了一个结构体来存储每种配料的酸甜度,然后就可以直接按你所想的那样直接递归了
#include<stdio.h>
#include<math.h>
int n,za=1,zs=0,ans=99999999;//za表示总酸度(酸度之积),zs表示总甜度(甜度之和),ans表示两者的绝对差
struct abc
{
int acid;
int sweet;
}a[20];
void dfs(int k)
{
if(k >= n)
return ;
else
{
za*=a[k].acid;
zs+=a[k].sweet;
if(ans > abs (za-zs))
ans=abs(za-zs);
dfs(k+1);//由0到n-1个配料逐个试探其酸度甜度绝对差
za/=a[k].acid;
zs-=a[k].sweet;
dfs(k+1);
}
}
int main()
{
int i,j;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d %d",&a[i].acid,&a[i].sweet);
dfs(0);
printf("%d",ans);
return 0;
}
题目:
很典型的一道dfs题,注意一下越界的情况和“已经走过的路”需要标记不要重复走的情况,基本就没有什么大问题了。当走到目的地时使方案+1然后回溯,重复循环,最终得到的方案数就是答案了。
#include<stdio.h>
int n,m,t,p,q,min=999999,w=0;
int a[51][51],book[51][51];
void dfs(int x,int y,int step)
{
int next[4][2]=
{{0,1},//向右走
{1,0},//下
{0,-1},//左
{-1,0}};//上
int tx,ty,k;
if(x == p && y == q)//判断是否到达XX的位置
{
w++;
return ;
}
//枚举四种走法
for(k=0;k<=3;k++)
{
//计算下一个点的坐标
tx=x+next[k][0];
ty=y+next[k][1];
//判断是否越界
if(tx<1 ||tx>n ||ty<1 ||ty>m)
{
continue;
}
//判断下一步的点是否为障碍物或者在该路线中已经走过
if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
book[tx][ty]=1;//标记这个点已经走过
dfs(tx,ty,step+1);//开始尝试下一个点
book[tx][ty]=0;//尝试结束,取消该点标记
}
}
return ;
}
int main()
{
int i,j,startx,starty;
int k1,k2;
//读入n和m分别为行与列
scanf("%d %d %d",&n,&m,&t);
//读入起点和终点坐标
scanf("%d %d %d %d",&startx,&starty,&p,&q);
//读入迷宫
for(i=1;i<=t;i++)
{
scanf("%d %d",&k1,&k2);
a[k1][k2]=1;
}
//从起点开始搜索
book[startx][starty]=1;//标记起点已在路径中,防止后面重复走
//第一个参数是起点的x坐标,第二个参数是起点的y坐标,第三个参数是初始步数为0
dfs(startx,starty,0);
printf("%d",w);
getchar();
return 0;
}
题目:
6.奇怪的电梯 - 洛谷
这题不难,类似于只有两个方向的“迷宫 - 洛谷”题(话说迷宫这题是真的很经典了),不过还有个额外的操作:“剪枝”。就是把多余的没用的部分剔除,使程序能够更快地运行,专业一点的说法是
所以可以编写出代码
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int n,A,B,flag=999999;//有n层楼。从A楼开始,目的地到B楼
int storey[10000],storey2[20000];//storey[]存储当层电梯的可移动楼层数,storey2[]表示是否已到达过这个楼层(同样的楼层无需重复考虑)
int min (int a,int b)
{
return (a>b?b:a);
}
void dfs(int x,int temp)
{
if(x==B)
{
//达到目标
flag=min(flag,temp);
return ;
}
else
{
if(temp < flag )
{
storey2[x]=1;//标记已到的x层,防止重复考虑相同楼层
if( (x+storey[x]) <= n &&storey2[x+storey[x]]==0)
{
// if(storey2[x+storey[x]]==0)
// {
dfs(x+storey[x],temp+1);
// }
}
if( (x-storey[x]) >= 1 && storey2[x-storey[x]]==0)
{
// if(storey2[x-storey[x]]==0)
// {
dfs(x-storey[x],temp+1);
// }
}
storey2[x]=0;//回溯
return ;
}
}
}
int main()
{
int i,j;
scanf("%d %d %d",&n,&A,&B);
for(i=1; i<=n; i++)
{
scanf("%d",&storey[i]);
}
//printf("%d",storey2[b]);
dfs(A,0);
//storey2[a]=1;
//printf("%d",storey2[b]);
if(flag==999999)
printf("-1");
else
printf("%d",flag);
return 0;
}
题目:
这道题要求在给出的正方形矩阵中准确找出固定词语:“yizhong”(话说这个确定不是哪个人的名字么。。。)
我的思路是先找到正方形矩阵中的所有“y”,并把这些“y”的坐标分别用两个数组记录下来,而后再判断这些“y”后面跟着的字母能不能组成一个完整的给定词,当然,这里要用循环,让每个“y”进入函数判断一遍。
这里要注意的是最好用%s进行输入,这样就不需要考虑“要不要加getchar()”的问题
思路理清之后就可以开始敲代码了:
#include<stdio.h>
int n;
char a[1000][1000],k1[2000],k2[2000];
//定义方向常量数组
int book[1000][1000];//用于标记数组
int next[8][2]={{0,1},{0,-1},{1,0},{-1,0},{-1,1},{-1,-1},{1,1},{1,-1}};
//右、左、上、下、下右、下左、上右、上左
void dfs(int x,int y,int k,int flag)
{
if(x < 0 ||x >=n || y < 0 || y >=n)
return;
int i,j;
if(k == -100)
{
for(i=0;i<8;i++)
{
int tx,ty;
tx=x+next[i][0];
ty=y+next[i][1];
if(tx < 0 || tx >= n || ty < 0 || ty >= n)
continue;
if(a[tx][ty] == 'i')
dfs(tx,ty,i,1);
k=-100;
}
}
if(k != -100)
{
int tx,ty,i1;
tx=x+next[k][0];
ty=y+next[k][1];
if(tx < 0 || tx >= n || ty < 0 || ty >= n)
return;
if(a[tx][ty] == 'z' && flag== 1)
dfs(tx,ty,k,2);
if(a[tx][ty] == 'h' && flag == 2)
dfs(tx,ty,k,3);
if(a[tx][ty] == 'o' && flag == 3)
dfs(tx,ty,k,4);
if(a[tx][ty] == 'n' && flag == 4)
dfs(tx,ty,k,5);
if(a[tx][ty] == 'g' && flag == 5)
{
book[tx][ty]='7';
book[tx-next[k][0]][ty-next[k][1]]='6';
book[tx-2*next[k][0]][ty-2*next[k][1]]='5';
book[tx-3*next[k][0]][ty-3*next[k][1]]='4';
book[tx-4*next[k][0]][ty-4*next[k][1]]='3';
book[tx-5*next[k][0]][ty-5*next[k][1]]='2';
book[tx-6*next[k][0]][ty-6*next[k][1]]='1';
return ;
}
}
return ;
}
int main()
{
scanf("%d",&n);
int i,j,k=0;
for(i=0;i<n;i++)
scanf("%s",a[i]);
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(a[i][j] == 'y')//记录所有'y'字母的坐标
{
k1[k]=i;
k2[k]=j;
k++;
}
}
}
for(i=0;i<k;i++)
dfs(k1[i],k2[i],-100,0);
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(book[i][j] == '1')
a[i][j]='y';
else
{if(book[i][j] == '2')
a[i][j]='i';
else
{if(book[i][j] == '3')
a[i][j]='z';
else
{if(book[i][j] == '4')
a[i][j]='h';
else
{if(book[i][j] == '5')
a[i][j]='o';
else
{if(book[i][j] == '6')
a[i][j]='n';
else
{if(book[i][j] == '7')
a[i][j]='g';
else
a[i][j]='*';
}}}}}}
}
}
for(i=0;i<n;i++)
{printf("%s",a[i]);
if(i != n-1)
printf("\n");}
return 0;
}
题目:
这是道比较简单的搜索题。从1开始累加,加到目标数值(用户输入的数)后可以直接输出出来,然后向前一步步回溯,同时利用循环逐渐增加相加的数值,这里要注意不能直接输出n(用户输入的数),最简单的形式也至少是两项相加(“1+(n-1)”),同时要注意最好是将相加的数值作为参数传递到下一次的函数调用中去。
#include<stdio.h>
int n,a[1000];
void dfs(int sum,int k,int k1)//sum表示当前总和,k表示(下一次)调用函数时用到的数组下标,k1表示本次调用函数总数sum需从k1加起
{
int i,j;
if(sum == n)
{
for(i=0;i<k-1;i++)
printf("%d+",a[i]);
printf("%d\n",a[k-1]);
return;//已达成目标,此时回溯到上一步
}
if(sum > n)
return;//若超限则回溯至上一步
if(sum < n)
{
for(i=k1;i<n;i++)//从k1加起,加到n-1时结束
{
a[k]=i;
dfs(sum+i,k+1,i);
a[k]=0;
}
}
}
int main()
{
scanf("%d",&n);
dfs(0,0,1);
return 0;
}
题目:
这道题要求我们求出水坑的数量。“W”代表“水”,“.”代表“旱地”。由题意可知,一个水地的辐射范围是周围八个格子,那么我们就和之前一样,先设置一个全局方向数组变量“next【8】【2】”
当遇到第一个“水”的时候就可以直接进入函数开始判断了,通过递归将这份“水”辐射的范围内的“水”以及扩展开来辐射的更多的“水”全部做上标记,然后就可以退出函数并使"水坑"数+1了,利用循环如此反复判断、遍历,最后就能直接得到正确答案了
#include<stdio.h>
#include<string.h>
int next[8][2]={{0,1},{0,-1},{1,0},{-1,0},{-1,1},{-1,-1},{1,1},{1,-1}};
//右、左、上、下、下右、下左、上右、上左
char a[1001][1001];
int n,m;
void dfs(int x,int y)
{
int i,j;
for(i=0;i<=7;i++)//向各个方向遍历
{
int tx,ty;
tx=x+next[i][0];
ty=y+next[i][1];
if(tx < 0 || tx >= n || ty < 0 || ty >= m)
//return;
continue;
if(a[tx][ty] == 'W')
{
a[tx][ty]='$';//标记“W”为“$”,防止重复访问
dfs(tx,ty);//搜索该点的扩展点
}
}
}
int main()
{
int i,j,flag=0;
scanf("%d %d",&n,&m);
memset(a,'0',sizeof(a));
flag=0;
for(i=0;i<n;i++)
scanf("%s",a[i]);
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
if(a[i][j]=='W')
{
flag++;//若访问到一个水坑,则直接使“水塘总数”+1
dfs(i,j);//去搜索这一点的八个方向
}
}
}
printf("%d\n",flag);
}
题目:
这题的题意很好理解:给你一个“地图”,将其中闭合圈内的数字“0”变成“2”。
只要注意一下“闭合圈”的定义就好了。例如一个矩阵
1 1 1 0 0 0
1 0 1 1 1 1
1 1 1 0 0 1
0 0 1 1 1 1
(这个矩阵只是我举个例子,题目里给出的矩阵是个四边等长的正方形矩阵)
能变成“2”的只有第2行和第3行的0,也就是“半包围”的“0”不算闭合圈内的数值。
明白了这点就很好做了
我的想法是将所有圈外0全部标记为-1,最后再遍历地图,将0变成2,-1变成0就可以了。
#include<stdio.h>
int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右上左下
int a[50][50];
int n;
void dfs(int x,int y)
{
int i,j;
if(a[x][y] != 0)
return ;
if(a[x][y] == 0 )
a[x][y]=-1;
if(x < 1 || x > n || y < 1 || y > n )
return;
for(i=0;i<4;i++)
{
int tx,ty;
tx=x+next[i][0];
ty=y+next[i][1];
dfs(tx,ty);
}
}
int main()
{
scanf("%d",&n);
int i,j;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&a[i][j]);
for(i=1;i<=n;i++)
{
dfs(i,1);
dfs(1,i);
dfs(n,i);
dfs(i,n);
}
int k=2,flag=0;
while(k--)
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(a[i][j]== 0 && flag==0)
a[i][j]=2;
if(a[i][j]== -1 && flag==1)
a[i][j]=0;
}
}
flag=1;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
printf("%d ",a[i][j]);
printf("\n");
}
return 0;
}