备战蓝桥day-7(递归回溯)
1、递归+回溯算法
大致思想:从开始若满足条件则一直往后递归下去,直到条件不符或者已完成任务回溯到前一个状态,用来找到所有符合条件的情况。
例题:
题目背景
猪猪 Hanke 得到了一只鸡。
题目描述
猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 10 种配料(芥末、孜然等),每种配料可以放 1 到 3 克,任意烤鸡的美味程度为所有配料质量之和。
现在, Hanke 想要知道,如果给你一个美味程度 n ,请输出这 10 种配料的所有搭配方案。
输入格式
一个正整数 n,表示美味程度。
输出格式
第一行,方案总数。
第二行至结束,10 个数,表示每种配料所放的质量,按字典序排列。
如果没有符合要求的方法,就只要在第一行输出一个 0。
输入输出样例
输入 #1复制
11
输出 #1复制
10 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1
说明/提示
对于 100% 的数据,n≤5000。
#include<bits/stdc++.h>
using namespace std;
vector<vector<int>> res; //存储所有组合
void backtrack(int step,int current_sum,vector<int>& path,int n){
if(step==10){
if(current_sum==n)
res.push_back(path); //若十种配料都齐了且总量为n则加入组合,否则返回回溯
return ;
}
for(int i=1;i<=3;i++){
int remaining=10-step-1;
int min_required=remaining*1;
int max_required=remaining*3;
int new_sum=current_sum+i;
//当前i无法形成有效组合,剪枝
if(new_sum+min_required>n) continue;
if(new_sum+max_required<n) continue;
path.push_back(i); //可以加入当前i,到下一种配料
backtrack(step+1,new_sum,path,n);
path.pop_back(); //回溯到前一个状态
}
}
int main(){
int n;
cin>>n;
if(n<10||n>30){
cout<<0<<endl;
return 0;
}
vector<int> path;
backtrack(0,0,path,n);
cout<<res.size()<<endl;
for(auto& v:res){ //增强for循环,自推导类型auto
for(int i=0;i<10;i++){
cout<<v[i]<<" ";
}
cout<<endl;
}
return 0;
}
2、最大公约数与数字查找
题目描述
将 1,2,…,9 共 9 个数分成三组,分别组成三个三位数,且使这三个三位数的比例是 A:B:C,试求出所有满足条件的三个三位数,若无解,输出 No!!!
。
//感谢黄小U饮品完善题意
输入格式
三个数,A,B,C。
输出格式
若干行,每行 3 个数字。按照每行第一个数字升序排列。
输入输出样例
输入 #1复制
1 2 3
输出 #1复制
192 384 576 219 438 657 273 546 819 327 654 981
说明/提示
保证 A<B<C。
upd 2022.8.3:新增加二组 Hack 数据。
大致思路:首先对输入的比例进行判断,如果有0出现是没有意义的直接输出No,然后将比例化为最简整数比,以便找出所有的组合,这里用到了找最大公约数函数gcd,即循环取余直至为0。然后要生成1~9组成的三个数形成对应比例,即a*k,b*k,c*k三个数。对k的取值进行限定,然后对生成的数进行逐位判断即可,数中不包含0,无重复数字,包含了0~9,这里用到了位掩码,即used |=(1<<d);//表示将第d位数字设置为1;used&(1<<d)//判断第d位是否为1.代码如下:
#include<bits/stdc++.h>
using namespace std;
//求最大公约数,循环取余
int gcd(int a,int b){
while(b){
int temp=a%b;
a=b;
b=temp;
}
return a;
}
//检查数字是1~9
bool check(int x,int y,int z){
int used=0; //用位掩码来表示各个位置的状态
int digits[9];
digits[0]=x/100; //百位
digits[1]=(x/10)%10; //十位
digits[2]=x%10; //个位
digits[3]=y/100;
digits[4]=(y/10)%10;
digits[5]=y%10;
digits[6]=z/100;
digits[7]=(z/10)%10;
digits[8]=z%10;
//数字不为0且不重复
for(int i=0;i<9;i++){
int d=digits[i];
if(d==0||used&(1<<d))//检查第d位是否出现过
return false;
used |=(1<<d); //将第d位设置为1
}
//确保数字包含1~9
return used==0b1111111110;
}
int main(){
int A,B,C;
cin>>A>>B>>C;
if(A==0||B==0||C==0){
cout<<"No!!!"<<endl;
return 0;
}
int g=gcd(gcd(A,B),C);
int a=A/g,b=B/g,c=C/g;
int low=100/a;
int high=999/c;
vector<vector<int>> result;
for(int k=low;k<=high;k++){
int x=a*k,y=b*k,z=c*k;
if(check(x,y,z))
result.push_back({x,y,z});
}
if(result.empty()){
cout<<"No!!!"<<endl;
}else{
for(const auto &ele:result){
cout<<ele[0]<<" "<<ele[1]<<" "<<ele[2]<<endl;
}
}
return 0;
}
3、矩阵旋转
方阵顺时针旋转90度:先把方阵转置,即将对角线两边对称的元素交换位置,然后再将每一行反转reverse,逆时针则先反转每一行再将对角线两边对称的元素交换位置。
例题:
题目描述
Scarlet 最近学会了一个数组魔法,她会在 n×n 二维数组上将一个奇数阶方阵按照顺时针或者逆时针旋转 90∘。
首先,Scarlet 会把 1 到 n2 的正整数按照从左往右,从上至下的顺序填入初始的二维数组中,然后她会施放一些简易的魔法。
Scarlet 既不会什么分块特技,也不会什么 Splay 套 Splay,她现在提供给你她的魔法执行顺序,想让你来告诉她魔法按次执行完毕后的二维数组。
输入格式
第一行两个整数 n,m,表示方阵大小和魔法施放次数。
接下来 m 行,每行 4 个整数 x,y,r,z,表示在这次魔法中,Scarlet 会把以第 x 行第 y 列为中心的 2r+1 阶矩阵按照某种时针方向旋转,其中 z=0 表示顺时针,z=1 表示逆时针。
输出格式
输出 n 行,每行 n 个用空格隔开的数,表示最终所得的矩阵
输入输出样例
输入 #1复制
5 4 2 2 1 0 3 3 1 1 4 4 1 0 3 3 2 1
输出 #1复制
5 10 3 18 15 4 19 8 17 20 1 14 23 24 25 6 9 2 7 22 11 12 13 16 21
说明/提示
对于50%的数据,满足 r=1
对于100%的数据 1≤n,m≤500,满足 1≤x−r≤x+r≤n,1≤y−r≤y+r≤n。
#include<bits/stdc++.h>
using namespace std;
//小矩阵的左上角坐标为x,y,k阶方阵
void spin(vector<vector<int>>& matrix,int x,int y,int k,int z){
vector<vector<int>> subMatrix(k,vector<int>(k));
for(int i=0;i<k;i++){
for(int j=0;j<k;j++)
subMatrix[i][j]=matrix[x+i][y+j];
}
if(z==0){
//顺时针旋转90度,先转置,按对角线交换元素,再反转每一行
for(int i=0;i<k;i++){
for(int j=i;j<k;j++){
swap(subMatrix[i][j],subMatrix[j][i]);
}
}
//反转每一行元素
for(int i=0;i<k;i++){
reverse(subMatrix[i].begin(),subMatrix[i].end());
}
//把旋转后的元素放回去
for(int i=0;i<k;i++){
for(int j=0;j<k;j++){
matrix[x+i][y+j]=subMatrix[i][j];
}
}
}else if(z==1){
//逆时针旋转90度,先反转行,在进行转置
for(int i=0;i<k;i++){
reverse(subMatrix[i].begin(),subMatrix[i].end());
}
for(int i=0;i<k;i++){
for(int j=i;j<k;j++){
swap(subMatrix[i][j],subMatrix[j][i]);
}
}
for(int i=0;i<k;i++){
for(int j=0;j<k;j++){
matrix[x+i][y+j]=subMatrix[i][j];
}
}
}
}
int main(){
int n,m;
cin>>n>>m;
vector<vector<int>> matrix(n,vector<int>(n));
int ans=1;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
matrix[i][j]=ans;
ans++;
}
}
int x,y,r,z;
for(int i=0;i<m;i++){
cin>>x>>y>>r>>z;
spin(matrix,x-1-r,y-1-r,2*r+1,z);
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cout<<matrix[i][j]<<" ";
cout<<endl;
}
return 0;
}