《蓝桥杯C++ AB组辅导课》笔记(Acwing)

2023年 c++ b组 省一+国二

题单链接:活动:蓝桥杯C++ AB组辅导课 - AcWing

第一讲 递归与递推(8/8)

AcWing 95. 费解的开关

/*
	给定一组5*5的矩阵,矩阵上的元素只有0和1两种状态,可以操作一位,将其元素改变,并且其上下左右位置的元素也要进行改变  
	求是否存在小于六次的操作,使得矩阵的元素全变为1,并且输出最小操作次数
 */
#include <iostream>
#include <cstring>

using namespace std;

const int N=10;

char ch[N][N],key[N][N];

//偏移量
int dr[5]={-1,1,0,0,0};
int dc[5]={0,0,-1,1,0};

void turn(int r,int c) //进行翻转操作
{
	for(int i=0;i<5;i++)
	{
		int tr=r+dr[i];
		int tc=c+dc[i];
		if(tr>=0&&tr<5&&tc>=0&&tc<5)
		{
			if(ch[tr][tc]=='0') ch[tr][tc]='1';
			else ch[tr][tc]='0';
		}
	}
}

int main()
{
	int all;
	cin>>all;
	while(all--)
	{
		for(int i=0;i<5;i++) scanf("%s",key[i]); //输入状态
		
		int res=7; //因为是求小于6次的次数,故初始化为大于6的数
		
		/*
			枚举第一行按的所有情况(即2^5=32),当第一行的情况固定时,第一行的状态此时只能由第二行改变
			故此时,当对第一行操作后,第一行如果还存在为0的元素,就只能且必须通过第二行的相同列来将其元素转换为1
			以此类推,第i行操作结束后由第i+1行的操作将第i行的元素全部转化为1,直到最后一行
			当最后一行存在0时,表明第一行的操作不合理,否则其所得到的次数就是在第一行固定操作下的最小次数
		*/
		for(int i=0;i<32;i++) //枚举第一行所有的情况
		{
			memcpy(ch,key,sizeof(ch)); //拷贝所给的初始状态
			int cnt=0; //记录操作次数
			for(int j=0;j<5;j++) //根据二进制来判断第一行的当前位数是否需要进行翻转
			{
				if(i>>j&1)
				{
					cnt++; //进行翻转,操作次数加一
					turn(0,j);
				}
			}
			
			for(int j=0;j<4;j++) //枚举0~4行,表明当其所在行操作结束后,其下一行应该怎样操作使得该行元素全为1
			{
				for(int k=0;k<5;k++)
				{
					if(ch[j][k]=='0') //当该元素ch[j][k]为0时,因为为了维护j-1行全为1(0行的操作是枚举得来的),故不能操作第j行及其上面的行的元素,
					{				  //又因为必须要使得ch[j][k]为1,故此时就只能通过ch[j+1][k]的操作来改变
						turn(j+1,k); //翻转ch[j+1][k],使得ch[j][k]为1
						cnt++; //操作次数加一
					}
				}
			}
			
			bool flag=true;
			for(int j=0;j<5;j++) //枚举最后一行是否存在0,即第一行的操作是否能够使得所有的元素变为1
			{
				if(ch[4][j]=='0') 
				{
					flag=false;
					break;
				}
			}
			
			if(flag) res=min(res,cnt); //如果能进行,则取最小操作次数
		}
		
		//输出结果
		if(res<=6) printf("%d\n",res); 
		else printf("-1\n");
	}
	
	return 0;
}

AcWing 116. 飞行员兄弟

对于4*4的格子,每个格子最多按一次,故可以枚举每个格子按或不按的情况,通过二进制来枚举这些情况,并进行比较,求最小操作次数只需要在可满足解中选出操作次数最小的情况,求字典序最小只需要保证在操作次数最小时,记录最先枚举到的情况即可,综上即可求出最优解。


第二讲 二分与前缀和(9/9)

AcWing 730. 机器人跳跃问题

/*
	给定1~n号建筑的高度,从高度为0的0号建筑出发
	假设现在的能量值为e,如果从高到底跳,则失去h(下一位建筑)-e的能量,如果从低到高跳,则得到e-h(下一位建筑)的能量
	要求在到达第n号建筑时,在整个过程中能量值不能为负数,求最小使用的能量值
  
	通过推理得出,无论是失去还是获得,能量值的变化为e -> 2*e-h(下一位建筑),由此可以通过假设初始能量大小来判断其是否可行
	并且初始的能量值越大,能完成游戏的成功率就越高,满足单调性可以进行二分  
*/
#include <iostream>

using namespace std;

const int N=1e5+10;

int n;
int num[N];

bool check(int x) //检查假设的初始能量值大小是否可行
{
	for(int i=1;i<=n;i++) 
	{
		x=2*x-num[i];
		if(x>=1e5) return true; //由于数据范围,最大高度为1e5,故当能量值为1e5时,就必定可以成功完成游戏
		if(x<0) return false; //出现负数,则不能完成
	}
	return true;
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>num[i];
	
	int l=0,r=1e5;
	while(l<r) //二分找出最终结果
	{
		int mid=(l+r)/2;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	cout<<r<<endl;
	
	return 0;
}

AcWing 1221. 四平方和

/*
	四平方和定理,又称为拉格朗日定理:每个正整数都可以表示为至多 4 个正整数的平方和。
	对于a*a+b*b+c*c+d*d=n,给定一个n,求所有满足条件的(a,b,c,d)中,字典序最小的解,此处a,b,c,d可以为0
	
	由于四重循环为超时,故先将c和d枚举出来,通过(c*c+d*d,c,d)升序排列
	再枚举a,b,通过n-a*a-b*b二分查找出相对应且字典序最小的c*c+d*d的c和d
	由于保证了a和b的字典序最小,且找相应c,d时也保证c,d的字典序在满足c*c+d*d=n-a*a-b*b时最小
	故第一次得到的满足解即为所求 
  
	三重循环会超时
	用map来存储mp[c*c+d*d]={c,d}会超时
*/
#include <iostream>
#include <algorithm>

using namespace std;

const int N=5e6+10;

int n,m;
bool st;
struct Node
{
	int s,c,d;
	bool operator < (const Node &t) const
	{
		if(s!=t.s) return s<t.s;
		if(c!=t.c) return c<t.c;
		return d<t.d;
	}
}node[N];

int find(int x)
{
	int l=0,r=m-1;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(x<=node[mid].s) r=mid;
		else l=mid+1;
	}
	return r;
}

int main()
{
	cin>>n;
	//预处理所有c和d的可能情况
	for(int c=0;c*c<=n;c++)
	{
		for(int d=c;c*c+d*d<=n;d++)
		{
			int s=c*c+d*d;
			node[m++]={s,c,d};
		}
	}
	sort(node,node+m); //保证s=c*c+d*d具备二分查找所需要的单调性,且相同的s下的c和d字典序最小
	
	//按字典序从小到大枚举a和b,并通过二分查找相应的最优c和d,故第一次可满足解即为最优解
	for(int a=0;a*a<=n;a++)
	{
		for(int b=a;a*a+b*b<=n;b++)
		{
			int t=n-a*a-b*b;
			int k=find(t);
			if(node[k].s==t)
			{
				cout<<a<<" "<<b<<" "<<node[k].c<<" "<<node[k].d<<endl;
				st=true;
				break;
			}
		}
		if(st) break;
	}
	
	return 0;
}

AcWing 99. 激光炸弹

边长覆盖的是格子,但是价值位于每个顶点上,注意辨别,进行二维前缀和即可。

AcWing 1230. K倍区间

/*
	给定一个长度为n的数列,如果其中一段连续的子序列之和为k的倍数,就称这个区间为k倍区间
	对于区间[i,j]分析,如果该区间为k倍区间,并且假设前缀和数组为sum,那么一定存在sum[i-1]%k==sum[j]%k
	故只需要对每一位前缀和查询其之前存在多少个前缀和同时模k之后仍旧相等
	特别,对于第0位,其前缀和模上k为0,该情况对应,当sum[i]%k==0时,[1,i]的区间为k倍区间
*/
#include <iostream>

using namespace std;

typedef long long LL;

const int N=1e5+10;

int n,k;
LL sum[N],cnt[N];

int main()
{
	cin>>n>>k;
	//计算前缀和
	for(int i=1;i<=n;i++)
	{
		cin>>sum[i];
		sum[i]+=sum[i-1];
	}
	
	LL res=0;
	cnt[0]++; //第0位前缀和应该被记录下
	for(int i=1;i<=n;i++)
	{
		res+=cnt[sum[i]%k]; //查找第i位之前存在多少位与sum[i]模k所产生余数相同的前缀和数组,即存在多少位以i结尾的k倍区间
		cnt[sum[i]%k]++; //把当前这一位存入计数数组里面,为后面求k倍区间提供条件
	}
	cout<<res<<endl;
	
	return 0;
}

第三讲 数学与简单DP(8/8)

AcWing 1211. 蚂蚁感冒

/*
	一根1~100的数轴,数轴上存在若干物体,物体初始在整数坐标上,按单位距离移动
	物体的初始值表示其所在坐标,正负表示移动方向,每两个物品相遇后会瞬间折返,求接触过第一个物品(包括其本身)的物品个数
  
	首先,两个物品相遇且瞬间折返的情况其实可以看成两个物品对穿
	对数轴上除第一个物品之外的所有物品存在四种分类情况,假设第一个物品向左
	1、位于物品左边且向左,此时永远不会与物品相遇
	2、位于物品右边且向右,此时永远不会与物品相遇
	3、位于物品左边且向右,此时一定会与物品相遇
	4、位于物品右边且向左,此时如果存在第三种状态的物品,那么就一定会和物品相遇,否则就永远不会
	第一个物品向右也同理 
*/
#include <iostream>
#include <cmath>

using namespace std;

const int N=60;

int n;
int place[N];
int lr,rl;

int main()
{
	cin>>n;
	for(int i=0;i<n;i++) cin>>place[i];
	
	for(int i=1;i<n;i++)
	{
		if(abs(place[0])>abs(place[i])&&place[i]>0) lr++; //记录位于第一个物品左边向右的物品个数
		else if(abs(place[0])<abs(place[i])&&place[i]<0) rl++; //记录位于第一个物品右边向左的物品个数
	}
	
	if(place[0]<0&&!lr||place[0]&&!rl) cout<<1<<endl; //如果对于第一个物品,不存在第三种情况,那么只有第一个物品自身能与之接触
	else cout<<lr+rl+1<<endl; //否则就是 与第一个物品向对的物品数量+与跟在第一个物品之后的数量+物品本身
	
	return 0;
}

AcWing 1212. 地宫取宝

/*
	本题给出n*m的矩阵,每个格子上面存在一个有价值的物品
	从左上角出发到右下角,只能向下或者向右走,当已拿取的物品价值全部小于当前所走到的格子的物品价值时,当前物品才能被拿取,当然也可以不拿
	且要求走到右下角时所拿取的物品个数恰好是k,求所有的方案数
*/
#include <iostream>

using namespace std;

const int N=60,M=15,mod=1000000007;

int n,m,k;
int w[N][N];
int f[N][N][M][M];
//数组f[i][j][u][v]数组为动态规划数组,[i,j]表示i行j列,u表示已拿取物品个数,v表示最后一个拿取的物品价值,也就是所拿取的物品的最大价值

int main()
{
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>w[i][j]; //输入每个物品的价值
			w[i][j]++; //价值范围是0~12,为方便表示未拿取物品的情况,故对所有价值加1
		}
	}
	
	f[1][1][1][w[1][1]]=1;
	f[1][1][0][0]=1;
	//左上角存在两种情况,拿取或者不拿
	
	for(int i=1;i<=n;i++) //枚举行
	{
		for(int j=1;j<=m;j++) //枚举列
		{
			if(i==1&&j==1) continue; //左上角的情况已经预处理
			for(int u=0;u<=k;u++) //枚举所有走到(i,j)时的已拿取个数
			{
				for(int v=0;v<=13;v++) //枚举所有已拿取的物品最大价值
				{
					/*
						对于f[i][j][u][v]由四个状态转移过来
						1.当从上方转移,且未拿取(i,j)上的物品,此时由f[i-1][j][u][v]转移而来
						2.当从左方转移,且未拿取(i,j)上的物品,此时由f[i][j-1][u][v]转移而来
						3.当从上方转移,且拿取(i,j)上的物品,此时由f[i-1][j][u-1][0~v-1]转移而来
						4.当从左方转移,且拿取(i,j)上的物品,此时由f[i][j-1][u-1][0~v-1]转移而来
					*/
					int &val=f[i][j][u][v];
					
					
					val=(val+f[i-1][j][u][v])%mod; //当从上方转移,且未拿取(i,j)上的物品
					val=(val+f[i][j-1][u][v])%mod; //当从左方转移,且未拿取(i,j)上的物品
					
					if(u>0&&v==w[i][j]) 
					{
						//当拿取(i,j)上的物品时,此时u必须大于0,因为(i,j)的物品必定被拿取
						//且最后一个拿取的的物品价值最大,也就是说此时v必须等于w[i][j]
						
						for(int c=0;c<v;c++) //枚举所有价值小于v,且物品个数比u小1的情况
						{
							val=(val+f[i-1][j][u-1][c])%mod; //当从上方转移,且拿取(i,j)上的物品
							val=(val+f[i][j-1][u-1][c])%mod; //当从左方转移,且拿取(i,j)上的物品
						}
					}
				}
			}
		}
	}
	
	int res=0;
	for(int i=0;i<=13;i++) res=(res+f[n][m][k][i])%mod; //枚举最大值的所有情况,求和即为所求
	cout<<res<<endl;
	
	return 0;
}

AcWing 1214. 波动数列


第四讲 枚举、模拟与排序(11/11)

AcWing 1210. 连号区间数

AcWing 1236. 递增三元组

AcWing 1229. 日期问题


第五讲 树状数组与线段树(3/9)


第六讲 双指针、BFS与图论(9/9)


第七讲 贪心(2/8)


第八讲 数论(1/8)


第九讲 复杂DP(0/9)


第十讲 疑难杂题(0/7)


第十一届蓝桥杯省赛第一场C++A/B组真题(5/6)

AcWing 2069. 网络分析


第十二届蓝桥杯省赛第一场C++A/B/C组真题(3/9)


第十二届蓝桥杯省赛第二场C++B组真题(4/5)

AcWing 3494. 国际象棋


第十三届蓝桥杯省赛C++组真题(10/24)


第十四届蓝桥杯省赛C++组真题(0/24)

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值