2020.2.10 算法基础例题总结( 2811 熄灯问题(枚举)2812恼人的青蛙(枚举) 2802小游戏(递归+回溯+剪枝/迷宫问题))

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时才交换顺序!
binary的写法
sort的写法
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来读入上一行的回车)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值