2811 熄灯问题(枚举)
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
bool test(int puzzle[][7],int press[][7]){
for(int i=2;i<=6;++i){//i代表列
for(int j=1;j<=5;++j){//j代表行
press[j][i]=(puzzle[j][i-1]+press[j][i-1]+press[j-1][i-1]+press[j+1][i-1]+press[j][i-2])%2;
}
}
for(int j=1;j<=5;++j){//最后一列的每一行
if((puzzle[j][6]+press[j][6]+press[j][5]+press[j-1][6]+press[j+1][6])%2!=0)//最后一列是不是全都灭了
return false;
}
return true;
}
int main(){
int puzzle[7][7],press[7][7];
memset(puzzle,0,sizeof(puzzle));
memset(press,0,sizeof(press));
for(int i=1;i<=5;++i){
for(int j=1;j<=6;++j)
cin>>puzzle[i][j];
}
bool end=false;
int c=1;
while(c<=32&&end==false){
end=test(puzzle,press);
if(end==true){
for(int i=1;i<=5;++i){
for(int j=1;j<=5;++j)
cout<<press[i][j]<<" ";
cout<<press[i][6]<<endl;
}
}
else{
++c;
press[1][1]++;
for(int k=1;k<=5;++k){
if(press[k][1]>1){
press[k][1]=0;
press[k+1][1]++;
}
}
}
}
return 0;
}
算法总结:
【枚举】
1.本道题中显然枚举棋盘每个点的状态会导致超时,我们应该发现第一行或第一列(短的那个)的状态确定后,为了到达最终状态,其他的位置状态是确定的。应该寻找这种特殊关系减少枚举数量(剪枝)。
2.多设置一圈数组作为“保护圈”简化操作
C++用法总结:
memset在cstring库中
2812恼人的青蛙(枚举)
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
class coordinate{
public:
int x;
int y;
};
template <class T>
bool Mycompare(T cA,T cB){
if(cA.x==cB.x)
return (cA.y<cB.y);
else
return (cA.x<cB.x);
}
coordinate steps[5002];
int main(){
//freopen("in.in","w",stdin);
int R,C,N;
cin>>R>>C>>N;
for(int i=0;i<N;++i){
cin>>steps[i].x>>steps[i].y;
}
sort(steps+0,steps+N,Mycompare<coordinate>);//函数返回值为false时交换
int max_step=2;
for(int i=0;i<=N-2;++i){
for(int j=i+1;j<=N-1;++j){//i号脚印和j号脚印为一条合理的路径上的1和2个脚印
int dx=steps[j].x-steps[i].x;
int dy=steps[j].y-steps[i].y;
if(steps[i].x-dx>=1&&steps[i].y-dy>=1&&steps[i].x-dx<=R&&steps[i].y-dy<=C)//不是第一步,continue
continue;
if(steps[i].x+(max_step)*dx>R||steps[i].x+(max_step)*dx<1)//x方向最大步数已经出格了就直接不考虑后面了(步长更大),有效减少时间
break;
if(steps[i].y+(max_step)*dy>C||steps[i].y+(max_step)*dy<1)//但是按照我们目前的这个排序规则,y方向越界还可以试下一个点(比如[2,5]在[3,1]前面,试下一个会让距离变小)
continue;
int step=2;
coordinate ctemp;
ctemp.x=steps[i].x+2*dx;
ctemp.y=steps[i].y+2*dy;
while(true){
if(ctemp.x>R||ctemp.y>C||ctemp.x<1||ctemp.y<1){//如果走出去了就停止,且为完整走出
if(step>max_step)
max_step=step;//只有完整地走出去了才能记录为max_step
break;
}
if(binary_search(steps,steps+N,ctemp,Mycompare<coordinate>)){
step++;
ctemp.x+=dx;
ctemp.y+=dy;
}
else
break;
}
}
}
if(max_step<=2)
cout<<0<<endl;
else
cout<<max_step<<endl;
return 0;
}
算法总结:
【枚举】
1.这种给了每一步的,显然应该对这些步来枚举,而不是对地来枚举。
2.利用STL库中自带的sort和binary_search来对输入的坐标进行排序可以大大提升效率
3.注意题中的剪枝操作(何时为break,何时为continue)。合适且正确的剪枝操作能提升枚举性能。例如本题中的比大小方式,应该注意有可能前一个点在后一个点的左上方(也就是x1<x2,y1>y2)那么此时对y进行剪枝时应使用的是continue而不是break。
c++用法总结:
1.binary_search和sort函数都在algorithm库中,他们可以对类进行排序,默认的顺序是<,但是我们可以通过重载<号或者传入一个比较函数来进行随意的排序。
注意:比较函数返回true时不交换顺序,返回false时才交换顺序!
2.把固定大小的二维数组传入函数作为形参的写法如下:
//声明处不需要写*,并且要给定列的大小
void A(int a[][5]){}
//调用处只需要写地址
int mian(){
int a[10][5];
A(a);
}
2802小游戏(递归+回溯+剪枝/迷宫问题)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
int w=1,h=1;
int direction[4]={0,1,2,3};//依次代表→↑←↓
bool effect(char* card,int* mark,int x,int y,int target_x,int target_y){
if(x==target_x&&y==target_y)
return true;
if(mark[y*(w+2)+x]!=1 && 0<=x && x<=w+1 && 0<=y && y<=h+1 && card[y*(w+2)+x]==' ')//没走过这个格子并且这个格子在接线里且这不是终点
return true;
return false;
}
void search(char* card,int* mark,int &now_x,int &now_y,int pre_direction,int now_direction,const int target_x,const int target_y,int & min_length,int& length){
int temp_x=now_x,temp_y=now_y;
if(now_direction==0)
temp_x++;
else if(now_direction==1)
temp_y++;
else if(now_direction==2)
temp_x--;
else
temp_y--; //现在即将进行判断的这个新位置
//cout<<"现在在测试点:"<<temp_x<<" "<<temp_y<<"最小长度为:"<<min_length<<"当前长度为:"<<length<<endl;
if(effect(card,mark,temp_x,temp_y,target_x,target_y)==true) {
mark[temp_y*(w+2)+temp_x]=1;
if(now_direction!=pre_direction)
length++;
//cout<<"现在在测试点:"<<temp_x<<" "<<temp_y<<"最小长度为:"<<min_length<<"当前长度为:"<<length<<endl;
if(length>=min_length){//这条线已经比最长的长了,就不继续这条线了
mark[temp_y*(w+2)+temp_x]=0;
if(now_direction!=pre_direction)
length--; //回溯
return;
}
if (temp_x==target_x && temp_y==target_y){//如果找到了
if(length<min_length)
min_length=length;
mark[temp_y*(w+2)+temp_x]=0;
if(now_direction!=pre_direction)
length--; //回溯
return;
}
else{//如果没到终点,搜其他的方向
int forbid=(now_direction+2)%4;
for(int k=0;k<=3;++k){
if(k==forbid)
continue;
search(card,mark,temp_x,temp_y,now_direction,k,target_x,target_y,min_length,length);
}
mark[temp_y*(w+2)+temp_x]=0;
if(now_direction!=pre_direction)
length--; //回溯
}
}
return;
}
int main(){
int count_card=0;
while(true){
count_card++;
scanf("%d %d",&w,&h);//h行w列
if(w==0 && h==0)
break;
char card[(h+2)*(w+2)];
int mark[(h+2)*(w+2)];
memset(card,' ',sizeof(card));
for(int i=1;i<=h;++i){
getchar();//读取回车
for(int j=1;j<=w;++j)
card[i*(w+2)+j]=getchar();
}
cout<<"Board #"<<count_card<<":"<<endl;
int count_pair=0;
int x1=1,y1=1,x2=1,y2=1;
while(true){
count_pair++;
cin>>x1>>y1>>x2>>y2;
if(x1==0){
cout<<endl;
break;
}
memset(mark,0,sizeof(mark));
mark[y1*(w+2)+x1]=1;
int length=1,min_length=999999;//这个长度设定待定,现在前进的方向
for(int t=0;t<=3;++t)
search(card,mark,x1,y1,t,t,x2,y2,min_length,length);
cout<<"Pair "<<count_pair<<": ";//后面要输出n segments或者impossible
if(min_length==999999)
cout<<"impossible."<<endl;
else
cout<<min_length<<" segments."<<endl;
}
}
return 0;
}
算法总结:
【递归】+【回溯】之【迷宫问题/贪食蛇问题】
1.递归使用栈模型进行思考,我们的目标是使得递归向着栈的出口方向进行。
2.迷宫问题的一般思路:
1)方向:使用(1,0),(0.1),(-1,0),(0,-1)等几个方向
2)合适的剪枝操作(重点):
①到达终点 ②超出棋盘范围 ③当前数据(步数),已经劣于已求得的最优数据
④有障碍 ⑤已经走过的地方(本代码中用mark数组标记) ⑥遍历时不往回走 eg(1,0)和(-1,0)
3)回溯:
①成功找到了,把中间变量回溯,return
②这一枝不是最优/显然不正确,把中间变量回溯,return
②该节点所有都遍历完了,恢复中间变量,return
C++用法总结:
1.不定长的二维数组如果需要传入函数,最好写成一维数组传入地址作为形参,然后进行换算。
a[i][j]=a[i*列数+j]
2.想输入空格作为字符:
使用getchar()函数
但是要注意的是getchar()同样也会读入回车字符,要在输入中形式考虑这个问题
具体用法参考本例76-80行(外重i循环开始的时候先有一个getchar来读入上一行的回车)