算法讲解:汉诺塔,滚石柱,N皇后,熄灯问题,拨钟问题,二进制密码锁,算24,三数之和,假币问题,逆序对的数量,递归求波兰表达式,2的幂次方表示,海拉鲁城堡,真假记忆碎片,寻找林克的回忆系列,加农...

本文代码皆是可运行代码,选用了逻辑和实现最简单的方式呈现,部分代码带有注解,可供初学者学习!【点赞+收藏】

目录

一、三指针:

二、汉诺塔:

三、N皇后问题:

四、熄灯问题:

五、二进制密码锁

六、快排(模板)

七、归并排序(模板)

八、逆序对的数量:

九、递归求波兰表达式:

十、2的幂次方表示:

十一、海拉鲁城堡: 

十二、真假记忆碎片:

十三、算24:

十四、骑士林克的怜悯

十五、击杀黄金蛋糕人马:

十六、拨钟问题:

十七、英杰们的蛋糕塔:

十八、寻找林克的回忆(1):

十九、寻找林克的回忆(2):

二十、寻找林克的回忆(3):

二十一、净化迷雾森林:

二十二、加农的入侵:

二十三、假币问题:

二十四、滚石柱:

二十五、算法学习心得:


一、三指针:

三指针的关键是将复杂度从O(n^{3})降到O(n^{2}),如何做到呢?

比如一般的思路就是用下面的三重for循环来逐个比对值,看看三数和会不会等于target,但这样的话时间复杂度就是n^{3}

for(int i=0;i<pos;i++)
for(int j=i+1;j<pos;j++)
for(int k=j+1;k<pos;k++)
{
 if(v[i]+v[j]+v[k]==target){
    cout << v[i] << ' ' << v[j]  << ' ' << v[k] << endl;
		}
}

下面的程序是优化后的,令i和j的功能不变,唯一改变的是k,k指向数组的末尾,且要保证j<k,此时循环内部用一个while循环,相当于固定了i和j只去移动k,因为数组是经过排序的(从小到大),如果移动k的过程中,三数之和大于了target,k就要往前移动1位,k--。终止的条件就是三数之和等于或小于k,如果是等于那么下面的if语句就会将其输出,如果是小于则略过if语句,j++进入下一次循环。

for(int i=0;i<pos;i++)
for(int j=i+1,k=pos;j<k;j++)
{
	while(j<k && v[i]+v[j]+v[k]>target) k--;
 	if(v[i]+v[j]+v[k]==target)
	{
		cout << v[i] << ' ' << v[j]  << ' ' << v[k] << endl;
	}
}

因此三指针可以理解为,固定其中两个指针,然后去移动另一个指针,从而将复杂度控制在平方级别。下面是程序:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

vector<int> v;
int target,n,pos;

void f(int pos)
{

	
	for(int i=0;i<pos;i++)
	for(int j=i+1,k=pos;j<k;j++)
	{
		while(j<k && v[i]+v[j]+v[k]>target) k--;
 		if(v[i]+v[j]+v[k]==target)
		{
		 	cout << v[i] << ' ' << v[j]  << ' ' << v[k] << endl;
		}
	}
}

int main()
{
	
	cin >> target >> n;
	for(int i=0;i<n;i++)
	{
		int m ;
		cin >> m;
		v.push_back(m);
		sort(v.begin(),v.end());	
	}
	for(int i=0;i<v.size();i++)
	{
		if(v[i]>target) pos=i-1;
	}
	f(pos);
}  

二、汉诺塔:

帮大家简单回顾一下汉诺塔是什么意思:汉诺塔说的是从前有三根杆,有n个圆盘,要将圆盘统统从第1根杆转移到第3根杆上。

这看似简单的问题,难点有2:

第1个难点:规定了第1根杆上的圆盘是从下到上,从大到小堆叠的,也就是说——最下面的盘子是最大的。转移到第3根杆上的时候,最下面的盘子也要是最大的。因为只能从最上方逐个取盘子,所以我们不能直接把盘子直接从第1根杆上直接转移到第3根杆上,那样最下方的盘子是最小的,不符合要求。

第2个难点:在转移的过程中,不能让上方的盘子大于下方的盘子。意思就是在转移的过程中,盘子的状态也必须是从下到上,从大到小。

所以我们分析一下这个问题:能不能通过另外一根杆子,先将第1根杆上的,除了最后的一个盘子,先转移到第2根杆上。然后再把第1根杆上剩下的那个大盘子转移到第3根杆上。再把第2根杆上的盘子按照这种操作全部转移到第3根杆上。

然而事情还远没有这么简单。首先,若想把第1根杆上的,除了最后的一个盘子,转移到第2根杆上,我们必须借助第3根杆,

怎么样?是不是感觉有点清晰了,这样“重复的操作”就是我们所称的递归。

而所谓的递归,通俗一点来说:就是执行重复的一段操作,只是数量的规模更小。就像上面的1和2,执行的操作都是一样的,就像抽丝剥茧一样,一层层直达内核,而这个内核就是我们所说的退出条件。

那这道题的退出条件是什么呢?

就是等到第1根杆上只剩下最后1个盘子的时候,直接将其转移到第3根杆上就可以啦。

接下来我将详细讲解一下算法,保证通俗易懂:

第1步:我们先定义一个“转移”函数,目的就是模拟盘子转移的过程。代码如下,将盘子从x转移到y,x和y分别代表的是杆子的代号,比如A,B:

void move(char x,char y)
{
	printf("%c->%c",x,y);
}

第2步:写递归函数和退出条件。先解释一下各参数的含义:n代表盘子数,start、end分别代表盘子转移过程的起点和终点。temp代表中间用于临时放置盘子的杆子。

当n等于1时,表明只剩下最后一个盘子,直接调用move函数,将盘子从第1根杆转移到第3根杆,然后退出。

void f(int n,char start,char temp,char end)
{
	if(n==1) 
	{
		move(start,end);
		return ;
	}
}

第3步:编写main函数,n表示盘子数,A,B,C分别代表3根杆子,A杆是起始杆,C杆是目标杆,B杆是调整杆:

int main()
{
	int n;
	cin >> n;
	f(n,A,B,C);
	return 0;
}

以上就是初始的准备工作,接下来开始讲解如何具体编写递归函数主体:

首先我们明确的一下步骤:

第1步:将第1根杆(start)上的,除最后一个盘,移动到第2根杆(temp)上。

f(n-1,start,end,temp);

可能看到这段代码会有一些困惑,因为我们的参数列表是如下图:

void f(int n,char start,char temp,char end)

大家可以这么理解:除去int n这个参数之后的第一个参数位置(start)就是第1根杆 起始杆,第二个参数位置(temp)就是第2根杆 调整杆,第三个参数位置(end)就是第3根杆 目标杆。

盘子传递的过程就是从第1根杆到第3根杆的过程,所以我们只需要改变start和end的传入参数,就可以模拟实现这个过程。

因此我们才将start放在第1个位置,temp放在第3个位置,目的就是为了让第1根杆上的盘转移到第2根杆上。

还可以这么理解:第1个参数位置就是盘子要转移的起始位置,第3个参数位置就是盘子要转移的终止位置。

第2步:将第1根杆上的剩下那个盘转移到第3根杆上:

move(start,end);

因为start是第1根起始杆嘛,end是第3根目标杆嘛,这点比较好理解。

第3步:将第2根杆上的所有盘子(n-1个)重复上述的步骤,全部移动到第3根杆子上。

f(n-1,temp,start,end) ;

这一步就比较跳跃了,除去n-1这个参数后,第一个参数传入temp代表第2根调整杆上的盘子,第三个参数传入end代表第3根目标杆。相当于就是把调整杆上的盘子全部按照上述的步骤转移到目标杆上。

下面就是递归每一步的

void f(int n,char start,char temp,char end)

//参数:n=3,start=A,temp=B,end=C
f(n-1,start,end,temp); 

//参数:n=2,start=A,temp=C,end=B
f(n-1,start,end,temp); 

//参数:n=1,start=A,temp=B,end=C
if(n==1) {move(start,end);return ;} //输出:A->C

//参数:n=2,start=A,temp=C,end=B 【回退】
move(start,end);                    //输出:A->B

//参数:n=2,start=A,temp=C,end=B
f(n-1,temp,start,end) ; 

//参数:n=1,start=C,temp=A,end=B
if(n==1) {move(start,end);return ;} //输出:C->B

//参数:n=3,start=A,temp=B,end=C 【回退】
move(start,end);                     //输出:A->C
    
//参数:n=3,start=A,temp=B,end=C
f(n-1,temp,start,end);  

//参数:n=2,start=B,temp=A,end=C
f(n-1,start,end,temp);

//参数:n=1,start=B,temp=C,end=A
if(n==1) {move(start,end);return ;}  //输出:B->A

//参数:n=2,start=B,temp=A,end=C 【回退】
move(start,end);                     //输出:B->C

//参数:n=2,start=B,temp=A,end=C
f(n-1,temp,start,end) ; 

//参数:n=1,start=A,temp=B,end=C
if(n==1) {move(start,end);return ;}  //输出:A->C

三、N皇后问题:

#include <iostream>
using namespace std;
const int N = 20;

// bool数组用来判断搜索的下一个位置是否可行
// col列,dg对角线,udg反对角线
// g[N][N]用来存路径

int n;
char g[N][N];
bool col[N], dg[N], udg[N];

void dfs(int u) {
    // u == n 表示已经搜了n行,故输出这条路径
    if (u == n) {
        
        for (int i = 0; i < n; i++) puts(g[i]);   // 等价于cout << g[i] << endl;
        puts("");  // 换行
        return;
    }

    // 枚举u这一行,搜索合法的列
    int x = u;
    for (int y = 0; y < n; y++)
        // 剪枝(对于不满足要求的点,不再继续往下搜索)  
        if (col[y] == false && dg[y - x + n] == false && udg[y + x] == false) {//x代表行,y代表列
            col[y] = dg[y - x + n] = udg[y + x] = true;
            g[x][y] = 'Q';
            dfs(x + 1);
            g[x][y] = '.';  // 恢复现场
            col[y] = dg[y - x + n] = udg[y + x] = false;
        }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            g[i][j] = '.';

    dfs(0);

    return 0;
}

四、熄灯问题:

这题原本想用bitset来做,但发现bitset无法测量读入二进制数的实际长度,因此只能用string来做

#include <bitset>
#include <cstring>
#include <iostream>
#include <memory>
using namespace std;

bitset<6> source[6],//初始化输入的灯矩阵,一个比特表示一盏灯 6行6列
light[5], //不停变化的灯矩阵 5行6列
res[5],//结果灯矩阵 5行6列
line; //某一行的开关状态,注意是开关状态而不是灯的状态

void Output(int t) //输出函数用于输出
{
	cout << "PUZZLE #" << t << endl;
	for (int i=0;i<5;i++)
	{
		for(int j=0;j<6;j++)
		{
			cout << res[i][j] << ' ';
		 } 
		 cout << endl;
	}
}


int main(){
	int T; //输入用例个数
	cin >> T;
	for (int t=1;t<=T;t++) //对每个用例处理
	{
	    //初始化灯矩阵
		for(int i=0;i<5;i++) 
		for(int j=0;j<6;j++)
		{
			int x;
			cin >> x;
			source[i].set(j,x); //对第i行j列赋值x
		}
		//第1行的6个按钮开关排列的64种情况,对每种情况单独处理
		for(int n=0;n<64;++n)
		{	
			for(int i=0;i<5;i++) light[i]=source[i]; //将source初始数据全部复制到light中
			line =n ; //将line表示成开关情况
			for(int i=0;i<5;i++)
			{
				res[i]=line; //将line的结果赋值到res数组中
				for(int j=0;j<6;++j) 
				{
					if(line.test(j)) //对每列逐位列举,看是否是1,若是1代表会按下该开关
					{
						if(j>0) light[i].flip(j-1); //该位左侧改变
						light[i].flip(j); //该位改变
						if(j<5) light[i].flip(j+1); //该位右侧改变
					}
				}
				if(i<4) light[i+1] ^= line; //下一行的数据与当前的数据进行异或,更新当前行按钮按下后,下一行的状态
				line=light[i]; //更新line的值
			}
			if(light[4].none())//如果第4行的灯全部熄灭
			{
				Output(t);//输出
				break;
			}
		}
	}
	return 0;
} 

五、二进制密码锁

这道题要注意两个点:第一点:第1个按钮是否按下,第1个按钮的情况会决定后面所有按钮。第二点:一定是按下不同位的下一个按钮。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

void flip(string & str,int i) { //用于取反某一位

	if(str[i]=='1') str[i]='0';
	else if(str[i]=='0') str[i]='1';
}

int main()
{
	string line;
	string lock; 
	int mint=10000;
	cin >> line;
	string startlock = line;
	cin >> line;
	string targetlock=line;
	int n = line.size();
	
	for(int p=0;p<=1;++p) //分2种情况,用于确定第1位是否要按下
//所以第1位的情况会决定后面所有的情况
	{
		lock = startlock;
		int t=0; //t用于统计个数
		int next = p;

		for(int i=0;i<n;i++)
		{
			if(next==1){
				if(i>0) flip(lock,i-1);
				flip(lock,i);
				if(i<n-1) flip(lock,i+1);
				++t;
			}
			if(lock[i]!=targetlock[i]) next=1; //用于保证延后一位进行取反
		
			else next=0;
		}
			
		if(lock==targetlock) mint=min(t,mint);
		
	}
	if(mint==10000) cout << "impossible";
	else cout << mint;
	return 0; 
}

六、快排(模板)

#include <iostream>
using namespace std;

int nums[100000];

void f(int left,int right)
{
	int l=left-1,r=right+1,mid=nums[(left+right)/2];
	if(left>=right) return;
	while(l<r)
	{
		do {l++;} while(nums[l]<mid);
		do {r--;} while(nums[r]>mid);
		if(l<r) swap(nums[l],nums[r]);
	}
	f(left,r);
	f(r+1,right);
}

int main()
{
	int n;
	cin >> n;
	int k;
	cin >> k;
	for(int i=0;i<n;i++)
	cin >> nums[i];
	f(0,n-1);
	cout << nums[k-1]<<endl;
}

七、归并排序(模板)

第1步:设置结束条件,左边大于或等于右边

第2步:设置中点,设置指针指向两段的最左侧

第3步:对两段分别进行递归

第4步:准备一个临时数组,将较小的数存放进入

第5步:进行收尾

第6步:将新的数组逐一赋值给旧的数组

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

int nums[100000];
int temp[100000];

void mergeSort(int left,int right)
{
	if(left>=right) return ;
	int mid = (left+right)>>1;
	int l=left,r=mid+1,k=0;
	mergeSort(left,mid);
	mergeSort(mid+1,right);
	while(l<=mid && r<=right){
		if(nums[l]<nums[r]) temp[k++]=nums[l++];
		else temp[k++]=nums[r++];	
	}
	while(l<=mid) temp[k++]=nums[l++];
	while(r<=right) temp[k++]=nums[r++];
	for(int i=left,k=0;i<=right;i++,k++) nums[i]=temp[k];
	
}

int main()
{
	int n;
	cin >> n;
	for(int i=0;i<n;i++) cin >> nums[i];
	mergeSort(0,n-1);
	for(int i=0;i<n;i++) cout << nums[i] << ' ';
	
}

八、逆序对的数量:

用的是归并排序。

注意返回值是LL型,不然会报错

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long LL;
const int N = 100007;
int n;
int numbers[N],tmp[N];

LL mergeSort(int nums[],int left,int right) //注意返回值是LL型
{
	if(left >= right) return 0;
	int mid = left+right >> 1;
	LL result = mergeSort(nums,left,mid)+mergeSort(nums,mid+1,right);
	int k=0,p=left,q=mid+1;
//下面是对排序完结果合并
	while(p<=mid && q<=right) if(nums[p]<=nums[q]) tmp[k++] = nums[p++];
	else{//nums[p]>nums[q]满足逆序对的条件
//因为是从小到大排序,所以p后面的数都比nums[p]大,所以p后面的数都能和nums[q]进行结合生成逆序对
		result += mid - p +1; //为什么是mid-(p-1)因为还要算上p自身
//比如一共有5个数,下标从0到4,中间mid是下标2,假如下标0的数大于nums[q],那么reult要加几个数呢?答案是3个:下标0,1,2的数,包含Mid
		tmp[k++]=nums[q++];
	}
//下面是收尾
	while(p<=mid) tmp[k++]=nums[p++];
	while(q<=right)tmp[k++]=nums[q++];
//下面是赋值回原数组
	for(int i=left,k=0;i<=right;i++,k++) nums[i]=tmp[k];
	return result;
}

int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&numbers[i]);
	cout << mergeSort(numbers,0,n-1) << endl; 
	return 0;
}

九、递归求波兰表达式:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

double f(){
	string str;
	cin >> str;
	switch(str[0])
	{
		case '*' : return f()*f();break;
		case '+' : return f()+f();break;
		case '-' : return f()-f();break;
		case '/' : return f()/f();break;
		default : return(stof(str));
	}
}
int main()
{
	printf("%.6lf",f());	
} 

#测试用例:* + 11.0 12.0 + 24.0 35.0
#输出用例:1357.000000

下图是示意图。cin的特性是:读入的数据以空格为分隔,一次只能读入(两个空格之间的)一个数据。如果读入的是运算符,就递归求得运算符左右两边的值。如果读入的是数值,就返回转化为浮点数后的值,进行运算。只有当一个运算符左右两边的递归运算结束(均返回值),才会结束该层的递归,其值作为上一层递归的运算参数值。

十、2的幂次方表示:

#include <iostream>
#include <bitset>
using namespace std;

int getlen(int i) //计算n的二进制数长度
{
	int len=0;
	while(i) //i>0就不停循环,直到为0退出
	{
		i >>= 1;
		++len;
	}
	return len;
}

void convert(int num,int k)
{
	if(k==0) return;
	int num_k = (num >> (k-1)) &1; //从最高位开始测试,获取最高位的二进制值
	if(!num_k) convert(num,k-1); //如果num_k等于0,就k-1,获取次高位的二进制以此类推。
	else //如果num_k等于1则进行处理
	{
		if(k!=getlen(num)) cout << "+";
		if(k==1) cout << "2(0)"; //说明指数仅剩1位,是2^0=1
		else if(k==2) cout <<"2";//说明指数剩2位,是2^1=2
		else
		{
			cout << "2(";
			convert(k-1,getlen(k-1));
			cout << ")";
		}
		convert(num,k-1);
	}
}

int main()
{
	int n;
	cin >> n;
	convert(n,getlen(n));
	return 0;
} 

十一、海拉鲁城堡: 

#include <iostream>
using namespace std;
const int N = 55;
int m,n,cntroom,roomarea,maxarea;
int room[N][N];
bool st[N][N];

int dx[4]={0,-1,0,1};
int dy[4]={-1,0,1,0};
//(0,-1)往左,(-1,0)往上,(0,1) 往右,(1,0)往下 


void dfs(int x,int y)
{
	st[x][y]=true;
	++ roomarea;
	for(int i=0;i<4;++i){
		int nx=dx[i]+x,ny=dy[i]+y;
		if(nx<0 || ny<0 || nx>=m || ny>=n )continue;
		if(st[nx][ny]) continue;
		if(room[x][y]>>i&1)continue; //注意是对当前所站的格子来判断朝向是否有墙,后面才能递归朝该方向行走,这一点千万要注意。
		dfs(nx,ny);
	} 
}

int main()
{
	cin >> m >> n;
	for(int i=0;i<m;i++)
		for(int j=0;j<n;j++)
		{
			cin >> room[i][j];
		}
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++)
		{
			if(!st[i][j]){
				++cntroom;roomarea=0;
				dfs(i,j);
				maxarea = max(maxarea,roomarea);
			}
		}
	}
	cout << cntroom <<endl;
	cout << maxarea;
}

十二、真假记忆碎片:

#include <iostream>
#include <cstring>
using namespace std;
const int N =9;
const int M=3;
int a[N][N],b[N][N];
bool st[N+1];
string memory[N]={{"530070000"},{"600195000"},{"098000060"},{"800060003"},{"400803001"},{"700020006"},{"060000280"},{"000419005"},{"000080079"}};

const int n = 9;

bool check_input(){
	string line;
	for(int i=0;i<N;i++)
	{
		cin >> line;
		if(line.size()!=N) return false; //长度不对
		for(int j=0;j<N;j++) {
			int t = line[j]-'0'; //字符转整数
			if(t<1 || t>N) { //出现不是1到9的数字 
				return false; 
			}
			if(a[i][j]!=0 && a[i][j]!=t) return false; //输入的值与初始值不匹配 
			b[i][j]=t; 
		}
	}
	return true;
}

bool check_row(){
	for(int i=0;i<N;i++){
		memset(st,false,sizeof(st));
		for(int j=0;j<N;j++)
		{
			int t = b[i][j];
			if(t<0 || t>N) return false;
			if(st[t]) return false;
			st[t]=true;
		}
	}
	return true;
}

bool check_col()
{
	for(int i=0;i<N;i++){
		memset(st,false,sizeof(st));
		for(int j=0;j<N;j++)
		{
			int t=b[j][i];
			if(t<0 || t>N) return false;
			if(st[t]) return false;
			st[t]=true;
		}
	}
	return true;
}

bool check_block(){
	for(int x=0;x<N;x += M)
		for(int y=0;y<N; y += M)
		{
			memset(st,false,sizeof(st));
			for(int dx=0;dx<M;dx++)
				for(int dy=0;dy<M;dy++) 
				{
					int t = b[x+dx][y+dy];
					if(t<0 || t>N) return false;
					if(st[t]) return false;
					st[t]=true;
				}
		}
	return true;
}

int main()
{
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++) 
		{
			a[i][j]=memory[i][j]-'0';
		}
	if(check_input() && check_row() && check_col() && check_block())
		cout << "Yes" << endl;
	else
		cout << "No" << endl;
	return 0;

}

十三、算24:

#include<iostream>
#include<cmath>
#define efs 1e-6 //PS1:注意efs的定义方式
using namespace std;

bool f(double n);
bool count24(double a[], int n);

int main()
{
	double arr[5] = { 0 };
	while (cin >> arr[0] >> arr[1] >> arr[2] >> arr[3] && arr[0] + arr[1] + arr[2] + arr[3] > 0)
	{
		if (count24(arr, 4))
			cout << "YES" << endl;
		else
			cout << "NO" << endl;
	}
}

bool f(double n) //PS2:注意浮点数一定要用小于很小数的方式来判断是否等于0
{
	if (fabs(n) < efs) //PS3:注意要对n取绝对值!!
		return true;
	else return false;
}

bool count24(double a[], int n)
{
	if (n == 1)
	{
		if (f(a[0] - 24)) //PS4:4数作运算的结果存在a[0]中
			return true;
		return false;
	}
	double b[5] = { 0 }; //PS5:定义的数组b专门用于存放n-1个数
    //选择任意不同的两个数进行运算
	for (int i = 0; i < n ; ++i) //PS6:千万千万注意,n的值是会变的而不是一直是4,会从4,3,2递减
	{
		for (int j = 0; j < n; ++j)
		{
			if (i == j)continue; //PS7:跳过取值相同的数
			int m = 0;
			for (int k = 0; k < n; ++k) //PS8:k是未被取到的剩余数的下标
			{
				if (k != i && k != j)
					b[m++] = a[k]; //把其余的数放到数组里
			}
            //把运算后的数放到数组里
            //下面可以视为是对4中情况,加减乘除分别作为一种情况进行递归
			b[m] = a[i] + a[j];
			if (count24(b, n - 1))//对运算后的n-1个数进行算24操作
				return true;
			b[m] = a[i] - a[j];
			if (count24(b, n - 1))
				return true;
			b[m] = a[i] * a[j];
			if (count24(b, n - 1))
				return true;
			if (a[j] != 0)b[m] = a[i] / a[j];
			if (count24(b, n - 1))
				return true;
		}
	}
	return false; //PS9:如果途中没有返回true最后要返回false
}

十四、骑士林克的怜悯

第1点:千万千万注意:这道题的X轴式字母的那条轴,而不是一般竖直的这条轴。原因是vector<pair<char, int>> path;我们设定了pair的第一个元素是字符型,第二个元素是整型,dfs传入的第一个参数x对应的是第一个字符型元素,因此x轴必须是横轴。所以一定要注意dx,dy数组值的填写。

第2点:这题还有一个陷阱,就是要输出字典序最小的路线,所谓字典序最小,就是从第1个字符到最后一个字符,与其它字符相比,都必须要是最小的序列,所以:for (int i = 0; i < q; i++)for (int j = 0; j < p; j++)就是将i视为横轴,将j视为纵轴,a[i][j]就是按照竖直方向进行搜索,A1比A2小比B1小,因为A的ascii码为97,1的ascii码为49。

#include <utility>
#include <string>
#include <vector>
#include <iostream>
using namespace std;

const int N = 27;
int p, q;
bool st[N][N];
vector<pair<char, int>> path;
int dx[8] = { -2,-2,-1,-1,1,1,2,2 };
int dy[8] = { -1,1,-2,2,-2,2,-1,1 };

bool dfs(int x, int y, int cnt)
{
	path.push_back({ char(x + 'A'),y + 1 });
	if (cnt == p * q) {
		for (auto a : path) cout << a.first << a.second;
		return true;
	}
	st[x][y] = true;
	for (int i = 0; i < 8; i++)
	{
		int a = x + dx[i], b = y + dy[i];
		if (a < 0 || a >= q || b < 0 || b >= p) continue;
		if (st[a][b]) continue;
		if (dfs(a, b, cnt + 1)) return true; //这点很重要,能保证输出的是字典序最小的。
	}
	st[x][y] = false;
	path.pop_back();
	return false;
}

int main()
{
	int T;
	cin >> T;
	for (int t = 1; t <= T; t++)
	{
		cout << "#" << t << ":" << endl;
		cin >> p >> q;
		path.clear();
		memset(st, 0, sizeof(st));
		bool flag = false;
		for (int i = 0; i < q; i++) //i代表横轴
			for (int j = 0; j < p; j++) //j代表纵轴
        //这样操作的目的是让字典序最小
			{ 
				if (dfs(i, j, 1)) {
					flag = true;
					break;
				}
			}
		if (!flag) cout << "none";
		cout << endl;

	}
}

十五、击杀黄金蛋糕人马:

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

#define _For(a,b,c) for(int a=b;a<=c;++a)
#define _RFor(a,b,c) for(int a=b;a>=c;--a)
#define Clear(a,b) memset(a,b,sizeof(a))
const int Inf=0x3f3f3f3f,Inf2=0x7fffffff;

int W,H,M; //宽,高,切成M块
int minMax[27][27][407];

int dfs(int wide,int height,int cutNumber)
{
	if(wide*height < cutNumber +1) //不够斩
	return Inf;
	if(cutNumber==0) //斩完毕
	return wide * height;
	if(minMax[wide][height][cutNumber]!=-1)
	return minMax[wide][height][cutNumber]; 
	int minMArea = 0;
	minMArea = Inf;
	for(int i=1;i<=wide-1;i++)
		for(int k=0;k<=cutNumber-1;k++)
		{
			int m1 = dfs(i,height,k);
			int m2 = dfs(wide-i,height,cutNumber-1-k);
			minMArea = min(minMArea,max(m1,m2));
		}
	for(int j=1;j<=height-1;j++)
		for(int k=0;k<=cutNumber-1;k++)
		{
			int r1=dfs(wide,j,k);
			int r2=dfs(wide,height-j,cutNumber-1-k);
			minMArea= min(minMArea,max(r1,r2));
		}
	return minMax[wide][height][cutNumber] = minMArea;
} 

int main()
{
	while(true){
		cin >> W >>H >>M;
		if(W==0 && H==0) break;
		Clear(minMax,-1);
		cout << dfs(W,H,M-1) <<endl;
	}
	return 0;
}

十六、拨钟问题:

思路:先递归给每个方案赋予一个拨动次数(0到4),直到9个时方案都被赋予完毕。然后遍历9种移动方案,将每种方案中的[全部时钟],都拨动当前方案所需拨动的次数。如果最后时钟都指向0点,就输出方案。

所以整道题的思路就是:递归赋予每种方案一个执行的次数然后执行,看执行后的结果是否能使所有时钟指针归0。怎么样,很神奇吧!!

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

int oriClocks[9]; //时钟初始状态
int clocks[9]; //计算过程中时钟状态
int moveTimes[9]={0}; //9个时钟,每个时钟被拨动的次数
int minTimes=1<<30; //存储最少的拨动次数
int result[9];  //存储最少的拨动方案 
string  moves[9]={"ABDE","ABC","BCEF","ADG","BDEFH","CFI","DEGH","GHI","EFHI"};


void dfs(int n) //当前是第n号方案
{
	if(n>=9) //每个方案都有拨动次数 
	{
		memcpy(clocks,oriClocks,sizeof(clocks));
		int times = 0;
		for(int i=0;i<9;i++){ //9种移动方案
			if(moveTimes[i]){ //该移动方案执行moveTimes[i]次
                //下面是改变时钟指向操作
				for(int k=0;k<moves[i].size();k++)  
				{
					// moves[i][k]-'A'获取的是时钟号,clocks[moves[i][k]-'A']代表的就是当前时钟的状态 
					clocks[moves[i][k]-'A'] = (clocks[moves[i][k]-'A']+moveTimes[i])%4;//更新时钟指向
					times += moveTimes[i]; //统计拨动的次数
				}
			}
		}
       //下面是判断是否成功,9个钟都指向0点
		bool flag = true;
		for(int i=0;i<9;i++) //遍历9个时钟 
			if(clocks[i]!=0){ //只要有一个闹钟不是指向0点 
				flag = false; //不成功 
				break;
			}
       //下面是成功情况
		if(flag && minTimes > times){ //最小拨动次数大于当前次数要更新
		
			minTimes = min(minTimes,times);
			memcpy(result,moveTimes,sizeof(result)); //这部关键,将最少移动序列拷贝到结果 
		}
		return;
	}
	
	for(int i=0;i<4;i++) //最多4次一定恢复到0点
	{
			moveTimes[n]=i; //给每个方案赋予一个执行的次数 
			dfs(n+1); //递归下一个方案
	}
		
	return ;
}

int main(){
	for(int i=0;i<9;i++) cin >> oriClocks[i];
	dfs(0);
	for(int i=0;i<9;i++)//第1重循环用于遍历9种方案,
		for(int k=0;k<result[i];k++) //result[i]存储的是每个方案需要执行的次数
        //必须用这种形式输出,因为某些方案可能执行不止一次!!
            cout << i+1 <<" "; //输出方案
}

十八、寻找林克的回忆(1):

#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
#include <utility>
#include <algorithm>
using namespace std;

const int N = 9;
int g[N][N];
bool st_row[N][N + 1], st_col[N][N + 1], st_block[3][3][N + 1];

bool dfs(int x, int y) {
	if (y == 9) x++, y = 0;
	if (x == 9) {
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) cout << g[i][j];
			cout << endl;
		}
		return true;
	}
	if (g[x][y] != 0) return dfs(x, y + 1); //这段是关键,一定要是return
	for (int t = 1; t <= 9; t++) {
		if (!st_row[x][t] && !st_col[y][t] && !st_block[x / 3][y / 3][t]) //注意x和y都要/3
		{
			st_row[x][t] = st_col[y][t] = st_block[x / 3][y / 3][t] = true;
			g[x][y] = t;
			if (dfs(x, y + 1)) return true; 
			g[x][y] = 0;
			st_row[x][t] = st_col[y][t] = st_block[x / 3][y / 3][t] = false;
		}
	}
	return false;
}

int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(NULL), std::cout.tie(NULL);

	memset(st_row, 0, sizeof(st_row));
	memset(st_col, 0, sizeof(st_col));
	memset(st_block, 0, sizeof(st_block));

	for(int i=0;i<9;i++)
		for (int j = 0; j < 9; j++)
		{
			char ch;
			cin >> ch;
			int t = ch - '0';
			g[i][j] = t;
			if (t != 0)
				st_row[i][t] = st_col[j][t] = st_block[i / 3][j / 3][t] = true;
		}
	dfs(0, 0);
	return 0;
}

十九、寻找林克的回忆(2):

#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
#include <utility>
#include <algorithm>
using namespace std;

const int N = 9;
int ones[1 << N], LOG[1 << N];
int row[N], col[N], block[3][3];

string str;

inline int lowbit(int x)
{
	return x & -x;
}

void init()
{
    //1<<N,其中N为10,相当于将1左移10位,十进制值为1024,减去1值为1023
    //1023的二进制值为111111111,一共9个1,所以row和col的一个位便相当于存储了9个1
    //初始化为全1,如果有该数则减掉该数
	for (int i = 0; i < N; i++) row[i] = col[i] = (1 << N) - 1; 
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)
			block[i][j] = (1 << N) - 1;
}

inline int get(int x, int y)
{
    //注意一个&符号是作与运算,看看三个对应位上是否都是1,如果是就说明这个数的下标没有出现过
	return row[x] & col[y] & block[x / 3][y / 3];
}

bool dfs(int cnt)
{
	if (!cnt) return true; //待填空的位置个数为0
	int minv = 10;
	int x, y;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
			if (str[i * 9 + j] == '.') //是.说明需要填写
			{
				int t = ones[get(i, j)]; //get(i,j)返回的是一个十进制数
//而ones数组可以返回十进制数中1的个数,将数赋值给t
				if (t < minv) //目的是要获取可填数最少的位置
//从可填数最少的位置一直到可填数最多的位置,这样dfs的入口会小,最后的情况数就会少
				{
					minv = t;
					x = i;
					y = j;
				}
			}
	for (int i = get(x, y); i; i -= lowbit(i))
	{
		int t = LOG[lowbit(i)]; //LOG数组可以获取lowbit所返回数的1的位置
		row[x] -= 1 << t; //相当于把第t位的1进行移除
		col[y] -= 1 << t;
		block[x / 3][y / 3] -= 1 << t;
		str[x * 9 + y] = '1' + t;
		if (dfs(cnt - 1)) return true;
		row[x] += 1 << t; //进行回溯反向操作
		col[y] += 1 << t;
		block[x / 3][y / 3] += 1 << t;
		str[x * 9 + y] = '.';
	}
	return false;
}



int main()
{
	for (int i = 0; i < N; i++) LOG[1 << i] = i;
	for (int i = 0; i < 1 << N; i++)
	{
		int s = 0;
		for (int j = i; j; j -= lowbit(j)) s++; 
		ones[i] = s; //ones数组用于记录某个数会有几个1
	}
	while (cin >> str, str[0] != 'e')
	{
		init();
		int cnt = 0;
		for (int i = 0, k = 0; i < N; i++)
			for (int j = 0; j < N; j++, k++) //这个地方k的值很巧妙
//第1轮k从0到8,第2轮从9到17,因为没设定k结束值,所以每次都会随i值的变化继续累加
				if (str[k] != '.')
				{
					int t = str[k] - '1'; //注意这里是减去字符1
//假如值是3,就要把1左移2位,得到100,因此是用3-1得到t的值为2
					row[i] -= 1 << t; 
					col[j] -= 1 << t;
					block[i / 3][j / 3] -= 1 << t;
				}
				else cnt++; //cnt表示的待填空的位置个数
		dfs(cnt);
		cout << str << endl;
	}
	return 0;
}

二十、寻找林克的回忆(3):

#include <iostream>
#include <algorithm>
using namespace std;

const int N=9,M=1<<N;
int ones[M],LOG[M];
int row[N],col[N],block[3][3];
int g[N][N];

int ans=-1;

inline int lowbit(int x)
{
	return x & -x;
}

void init()
{
	for(int i=0;i<N;i++) LOG[1<<i]=i;
	for(int i=0;i<M;i++)
		for(int j=i;j;j-=lowbit(j))
			ones[i] ++;
	for(int i=0;i<9;i++) row[i]=col[i]=block[i/3][i%3]=M-1;
}

inline int get(int x,int y)
{
	return row[x] & col[y] & block[x/3][y/3];
}

inline int get_score(int x,int y,int t)
{
	return(min(min(x,8-x),min(y,8-y))+6)*t;
}

inline void draw(int x,int y,int t) //t传入的是读入的数值
//如果t为正则将row、col、block数组的那位清0
//如果t为负则将row、col、block数组的那位置1
{
	int s=1;
	if(t>0) g[x][y]=t;
	else //t为负,相当于是回溯的情况,看第73行前后的代码
	{
		s = -1;  //s取反
		t = -t;  //将t变为整数方便操作
		g[x][y]=0;
	}
	t--; //t减1位,移位时才能移动到正确位置
	row[x] -= (1<<t) * s;
	col[y] -= (1<<t) * s;
	block[x/3][y/3] -= (1<<t) * s;
}

void dfs(int cnt,int score)
{
	if(!cnt){
		ans = max(ans,score);
		return;
	}
	int minv = 10;
	int x,y;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
			if(!g[i][j])
			{
				int t = ones[get(i,j)];
				if(t<minv)
				{
					minv = ones[get(i,j)];
					x=i,y=j;
				}
			}
	for(int i=get(x,y);i;i -= lowbit(i)) //简化了
	{
		int t=LOG[lowbit(i)]+1;
		draw(x,y,t);
		dfs(cnt-1,score+get_score(x,y,t));
		draw(x,y,-t);
	}
}

int main()
{
	init();
	int cnt=0,score=0;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
		{
			int t;
			cin >> t;
			if(t)
			{
				draw(i,j,t);
				score += get_score(i,j,t);
			}
			else cnt++;
		}
	dfs(cnt,score);
	cout << ans << endl;
	return 0;
}

二十一、净化迷雾森林:

#include <iostream>
#include <algorithm>
#include <queue> 
using namespace std;

typedef pair<int, int> PII;
const int N = 25;
int n, m;
char g[N][N];

int bfs(int sx, int sy)
{
	queue<PII> q;
	q.push({ sx,sy });
	g[sx][sy] = '#';
	int res = 0;
	int dx[] = { -1,0,1,0 };
	int dy[] = { 0,1,0,-1 };
	while (q.size()) {
		auto t = q.front();
		q.pop();
		res++; //计数加在for循环外,否则会少计算初始位置
		for (int i = 0; i < 4; i++) {
			int x = t.first + dx[i];
			int y = t.second + dy[i];
       //一定要写成g[x][y]!='.',而且x和y必须是要大于等于,不能漏掉这个等于
			if (x < 0 || x >= n || y < 0 || y >= m || g[x][y] != '.') continue;
			g[x][y] = '#';
			q.push({ x,y });
		}
	}
	return res;
}

int main()
{
	while (cin >> m >> n, n || m)
	{
		for (int i = 0; i < n; i++) cin >> g[i];
		int x, y;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				if (g[i][j] == '@') {
					x = i;
					y = j;
				}
		cout << bfs(x, y) << endl;
	}
	return 0;
}

二十二、加农的入侵:

#include <iostream>
#include <algorithm>
#include <queue> 
#include <vector>
using namespace std;

typedef pair<int, int> PII;
const int N = 110;
int n, m; //n表示行,m表示列
PII start;
char g[N][N];
int dist[N][N];

const int dx[8] = { 1,1,1,0,0,-1,-1,-1 };
const int dy[8] = { -1,0,1,-1,1,-1,0,1 };

int bfs()
{
	memset(dist, -1, sizeof dist);
	queue<PII> q;
	q.push(start);
	dist[start.first][start.second] = 0;
	int res = 0;
	
	while (q.size())
	{
		auto t = q.front();
		q.pop();
		for (int k = 0; k < 8; k++)
		{
			int x = t.first + dx[k];
			int y = t.second + dy[k];
			if (x<1 || x>n || y < 1 || y>m) continue;
			if (g[x][y] == '*' || dist[x][y] != -1) continue;
			dist[x][y] = dist[t.first][t.second] + 1;
			res = max(res, dist[x][y]);
			q.push(make_pair(x, y));
		}
	} 
	return res;
}

int main(){
	cin >> m >> n >> start.second >> start.first;
	start.first = n + 1 - start.first;
	for (int i = 1; i <= n; i++) cin >> g[i] + 1;
	cout << bfs() << endl;
	return 0;
}

二十三、假币问题:

核心思想是:对所有硬币依次假设其为假币,再分2种情况:假币更轻,假币更重。因为只有一枚假币,如果是even表示天平持平,被假定为假币的硬币不应该出现在两侧,如果出现说明该硬币不是假币;如果是up表示右边天平更高,如果轻的假币不在右天平返回False,如果重的假币不在左天平返回False;如果是down表示右天平更低,情况与up相反。

#include <iostream>
#include <queue>
typedef long long LL;

using namespace std;

const int N = 100007;

int n;
int temp[N];
int A[N];



int MergeSort(int A[], int left, int right)
{
	if (left >= right) return 0;
	int mid = (left + right) >> 1;
	int l = left, r = mid + 1, k = 0;
	LL res = MergeSort(A,l, mid) + MergeSort(A,r, right);
	while (l <= mid && r <= right)
	{
		if (A[l] <= A[r]) temp[k++] = A[l++];
		else {
			temp[k++] = A[r++];
			res += mid - l + 1;
		}
	}
	while (l <= mid) temp[k++] = A[l++];
	while (r <= right) temp[k++] = A[r++];
	for (int i = left, k = 0; i <= right; i++, k++)
	{
		A[i] = temp[k];
	}
	return res;
}



int main()
{
	
	scanf("%d",&n);
	for (int i = 0; i < n; i++)
	{
		cin >> A[i];
	}
	cout << MergeSort(A, 0, n - 1);
	return 0;

}

二十四、滚石柱:

#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <bitset>
using namespace std;

const int N = 507;

struct Stone //Stone的数据结构
{
	int x, y, status;
	Stone(int a, int b, int c) { x = a; y = b; status = c; }
	Stone() :x(0), y(0), status(0) {};
};

void displayStone(Stone& s) //测试
{
	cout << "(" << s.x << " " << s.y << " " << s.status << ")";
}

//横躺时以施主左侧为基准,竖躺时以石柱上侧为基准
int direction[4][3][3] = {
//第一个参数:上下左右滚动
//第二个参数:不同姿势(竖,横塘,竖躺)滚后的状态
//第三个参数:是横纵坐标和状态
	{{-2,0,2},{-1,0,1},{-1,0,0}}, //上
	{{1,0,2},{1,0,1},{2,0,0}}, //下
	{{0,-2,1},{0,-1,0},{0,-1,2}},  //左
	{{0,1,1},{0,2,0},{0,1,2}}};  //右

Stone moveStone(Stone& p, int udlr) //返回下一个状态坐标
{
	int dx = direction[udlr][p.status][0];
	int dy = direction[udlr][p.status][1];
	int newstatus = direction[udlr][p.status][2];
	return Stone(p.x + dx, p.y + dy, newstatus);
}

int n, m;
char area[N][N];
int dist[N][N][3]; //标记是否走过

queue<Stone> q;
Stone start, target, qHead;

bool isVisited(Stone node) //是否走过
{
	return dist[node.x][node.y][node.status] != -1;
}

bool isInside(int x, int y) //是否在区域内
{
	return x >= 0 && x < n&& y >= 0 && y < m;
}

bool isValid(Stone node) //判断走的是否有效
{
	if (!isInside(node.x, node.y) || area[node.x][node.y] == '#') //不在区域内 || 踩到禁区
		return false;
    //竖躺:如果另一块不在区域内 || 另一块踩到禁区
	if (node.status == 2 && (!isInside(node.x + 1, node.y) || area[node.x + 1][node.y] == '#'))
		return false;
    //横躺:如果另一块不在区域内 || 另一块踩到禁区 
	if (node.status == 1 && (!isInside(node.x, node.y + 1) || area[node.x][node.y + 1] == '#'))
		return false;
    //直立:如果踩到易碎地
	if (node.status == 0 && area[node.x][node.y] == 'E')
		return false;
	return true;
}

char readChar() //取出字符
{
	char c = getchar();
	while (c != '#' && c != '.' && c != 'X' && c != 'O' && c != 'E') //不在符号内,就while跳过这些空格
		c = getchar();
	return c;
}

void BuildMap(int n, int m) //初始化,建立地图
{
	memset(area, '#', sizeof(area)); //初始化为禁区
	memset(dist, -1, sizeof(dist)); //初始化为-1表示未读
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			area[i][j] = readChar(); //读入数据,建立地图

	for(int i=0;i<n;i++)
		for (int j = 0; j < m; j++)
		{
			char c = area[i][j];
			if (c == 'X') //寻找起点
			{
				start.x = i,start.y = j,start.status = 0,area[i][j] = '.'; //找到起点,先假设为站立,但起点可能不止一个,因为可能横躺也可能竖躺
				if (isInside(i, j + 1) && area[i][j + 1] == 'X') //横躺
					start.status = 1, area[i][j + 1] = '.'; //更新状态
				if (isInside(i + 1, j) && area[i + 1][j] == 'X') //竖躺
					start.status = 2, area[i + 1][j] = '.'; //更新状态
			}
			if (c == 'O') //终点
				target.x = i, target.y = j, target.status = 0;
		}
}



int bfs(Stone& s)
{
	while (q.size())
		q.pop();
	q.push(s);
	dist[s.x][s.y][s.status] = 0;
	while (q.size())
	{
		qHead = q.front();
		q.pop();
		for (int i = 0; i < 4; i++)
		{
			Stone next = moveStone(qHead, i);
			if (!isValid(next))  continue;
			if (!isVisited(next))
			{
				dist[next.x][next.y][next.status] = dist[qHead.x][qHead.y][qHead.status] + 1;
				q.push(next);
				if (next.x == target.x && next.y == target.y && next.status == target.status)
					return dist[next.x][next.y][next.status];
			}
		}
	}
	return -1;
}

int main()
{
	while (cin >> n >> m && n)
	{
		BuildMap(n, m);
		int res = bfs(start);
		if (res == -1)
			cout << "Impossible" << endl;
		else
			cout << res << endl;
	}
}

二十五、骑士迅游问题 

题目描述:骑士在8*8的国际象棋棋盘上进行巡游,当指定骑士出发的位置后(x,y),能输出骑士遍历棋盘的所有路径坐标。

输出效果:

  

代码(请在visual stdio下运行,Dev-C++无法编译vector):

#include <utility>
#include <string>
#include <vector>
#include <iostream>
using namespace std;

const int N = 27;
int p, q;
bool st[N][N];
vector<pair<int, int>> path;
int dx[8] = { -2,-2,-1,-1,1,1,2,2 };
int dy[8] = { -1,1,-2,2,-2,2,-1,1 };

bool dfs(int x, int y, int cnt){
	path.push_back({ x,y });
	if (cnt ==64) {
		for (auto a : path) cout << "(" << a.first <<"," << a.second << ")" << endl;
		return true;
	}
	st[x][y] = true;
	for (int i = 0; i < 8; i++)
	{
		int a = x + dx[i], b = y + dy[i];
		if (a < 1 || a > 8 || b < 1 || b > 8) continue;
		if (st[a][b]) continue;
		if (dfs(a, b, cnt + 1))return true; //这点很重要,能保证输出的是字典序最小的。
	}
	st[x][y] = false;
	path.pop_back();
	return false;
}

int main(){
	int i, j;
	cin >> i >> j;
	path.clear();
	memset(st, 0, sizeof(st));
	bool flag = false;
	if (dfs(i, j, 1)) {
		flag = true;
	}
}

【如何解决了问题,烦请点赞+收藏】 

二十六、算法学习心得:

学习算法,最重要的是要把基础先吃透,比如:vector之类的STL的基本操作要懂,引用变量的形式要懂......不然稍难一点的算法题解都看不懂。

因为稍难的算法题解是由一些比较进阶的用法组成的,所以在看题解的时候可以先从main函数看起,将每个部分进行拆分,然后再逐一深入到每个函数内部看具体的功能。先进行拆分,逐一弄懂,再合起来进行理解。对着程序敲一遍,然后最重要的是要自己思考动手敲一遍。有些还是不理解的地方一定要多多注意,这些地方是值得深入进行探究的,如果能学会这些方法,甚至可以对不同的题目进行融会贯通。

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值