本文代码皆是可运行代码,选用了逻辑和实现最简单的方式呈现,部分代码带有注解,可供初学者学习!【点赞+收藏】
目录
一、三指针:
二、汉诺塔:
三、N皇后问题:
四、熄灯问题:
五、二进制密码锁
六、快排(模板)
七、归并排序(模板)
八、逆序对的数量:
九、递归求波兰表达式:
十、2的幂次方表示:
十一、海拉鲁城堡:
十二、真假记忆碎片:
十三、算24:
十四、骑士林克的怜悯
十五、击杀黄金蛋糕人马:
十六、拨钟问题:
十七、英杰们的蛋糕塔:
十八、寻找林克的回忆(1):
十九、寻找林克的回忆(2):
二十、寻找林克的回忆(3):
二十一、净化迷雾森林:
二十二、加农的入侵:
二十三、假币问题:
二十四、滚石柱:
二十五、算法学习心得:
一、三指针:
三指针的关键是将复杂度从O()降到O(),如何做到呢?
比如一般的思路就是用下面的三重for循环来逐个比对值,看看三数和会不会等于target,但这样的话时间复杂度就是。
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;
}
}
二十五、算法学习心得:
学习算法,最重要的是要把基础先吃透,比如:vector之类的STL的基本操作要懂,引用变量的形式要懂......不然稍难一点的算法题解都看不懂。文章来源:https://www.toymoban.com/news/detail-526005.html
因为稍难的算法题解是由一些比较进阶的用法组成的,所以在看题解的时候可以先从main函数看起,将每个部分进行拆分,然后再逐一深入到每个函数内部看具体的功能。先进行拆分,逐一弄懂,再合起来进行理解。对着程序敲一遍,然后最重要的是要自己思考动手敲一遍。有些还是不理解的地方一定要多多注意,这些地方是值得深入进行探究的,如果能学会这些方法,甚至可以对不同的题目进行融会贯通。文章来源地址https://www.toymoban.com/news/detail-526005.html
到了这里,关于C++学习算法心得和部分算法讲解(三指针)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!