第十五届蓝桥杯省赛
1.握手问题
2.小球反弹(难)


/*
考点:速度分解
分解为x轴往返,y轴往返(回到左上角起点)
假设x轴做了p个往返,y轴做了q个往返,时间为t
速度可以当做是x轴15,y轴17
x轴路程:dx*t=p*2*x
y轴路程:dy*t=q*2*y
两式相除
dx/dy=p/q*x/y
即(dx*y)/(dy*x)=p/q
可以把分母看作p,分子看作q (因为不想算除法)
p,q需要约分通过gcd找最大公约数约分
由p求t->t=2px/dx
*/
#include<bits/stdc++.h>
using namespace std;
int gcd(int p,int q){if(q==0){return p;}else{return gcd(q,p%q);}
}
int main(){int x=343720;int y=233333;int dx=15;int dy=17;int p=dx*y;int q=dy*x;int g=gcd(p,q);//g为p,q的最大公约数p=p/g;q=q/g;double t=p*2*x/dx;//时间double s=t*sqrt(15*15+17*17);//总路程printf("%.2f",s);return 0;
}
核心:(dx*y)/(dy*x)=p/q
需要记忆的模版(最大公约数)gcd
int gcd(int p,int q){if(q==0){return p;}else{return gcd(q,p%q);}
}
3.好数
//考察取每一位
#include<bits/stdc++.h>
using namespace std;
bool good(int a){//如果是好数返回true
int i=0;
while(a!=0){
i++;
int d=a%10;//当前位的数字
if(i%2!=0){//奇数位
if(d%2==0){return false;
}
}
else if(i%2==0){//偶数位
if(d%2!=0){return false;
}}
a/=10;
}
return true;
}
int main(){int n;cin>>n;int cnt=0;for(int i=1;i<=n;i++){
if(good(i)){// cout<<i<<endl;cnt++;
}}// cout<<"------------------------------------------";cout<<cnt;return 0;
}
4.R格式(难)
考点:高精度*低精度
答案代码
/*
分析:
求2^n*d
2的n次方当n为1000时非常大所以要用高精度算法
d很小为低精度
所以本题考:高精度*低精度
高精度可以用数组模拟
*/
#include<bits/stdc++.h>
using namespace std;
const int N=1300;
int a[N];//将字符串每一位转为整型
string s;//存放低精度浮点数
int main(){
int n;
cin>>n>>s;reverse(s.begin(),s.end());//高精度算法因为可能涉及进位,所以先把字符串翻转,则原来的最高位进位变成现在往右增加一位
int pos=s.find('.');//小数点位置
s.erase(pos,1);//删除小数点,因为本题最后四舍五入与小数点无关。从pos开始删一位就是把小数点删除了
int len=s.size();
//字符串转整型
for(int i=0;i<len;i++){a[i+1]=s[i]-'0';
}
//乘n个2
for(int i=0;i<n;i++){//每一位乘2//扫描for(int j=1;j<=len;j++){a[j]*=2;}
//处理进位问题
for(int j=1;j<=len;j++){if(a[j]>=10){a[j+1]++;a[j]%=10;if(j==len){//最高位
len++;}}}
}
//小数点四舍五入
if(a[pos]>=5){a[pos+1]++;//四舍五入后进位问题for(int i=pos+1;i<=len;i++){if(a[i]>=10){a[i+1]++;a[i]%=10;if(i==len){len++;}}}
}
//反着输出
for(int i=len;i>=pos+1;i--){cout<<a[i];
}return 0;
}
分析代码

5.宝石组合(难)
考点:唯一分解定理
代码:
/*
分析:
考点:唯一分解定理
任何一个大于1的自然数,如果他不是质数,那么他可以分解为有限个质数的乘积
例如:A,B为两个大于1的自然数
A=p1^a1*p2^a2*p3^a3*...pn^an
B=p1^b1*p2^b2*p3^b3*...pn^bnp1,p2...pn为质数
a1~an,b1~bn为指数
最大公因数:gcd(A,B)=p1^min(a1,b1)*p2^min(a2,b2)*...*pn^min(an,bn)
最小公倍数:lcm(A,B)=p1^max(a1,b1)*p2^max(a2,b2)*...*pn^max(an,bn)
由此可得
假设a,b,c的公共质因子(底数)的指数分别为x,y,z
则(乘相当于指数相加,除相当于指数相减)
s可以看做x+y+z+max(x,y,z)-max(x,y)-max(x,z)-max(y,z)->答案应该是x,y,z中一个数
所以为使得s最大则只需要找到最大公约数
*/#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6;
int a[N];
vector<int>fac[N];//存放每个数的因子。因为因子是不断放进来所以用动态数组vector,fac[i][j]表示i的第j个因子
vector<int>s[N];//s[i][j]表示i的第j个倍数,i为因子s[i]是找出以i为因子的所有数组成的数组
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){cin>>a[i];
}
//因为要输出字典序最小,所以先对a从小到大排序
sort(a+1,a+1+n);
//求每个数字的因子
for(int i=1;i<=1e5;i++){for(int j=i;j<=1e5;j+=i){//j为i的倍数
fac[j].push_back(i);//i是j的因子}
}
//求各因子对应的倍数(倍数是a数组里的数)
for(int i=1;i<=n;i++){//遍历a数组,求a数组里每个数的所有因子对应的a数组的数for(int j=0;j<fac[a[i]].size();j++){s[fac[a[i]][j]].push_back(a[i]);//表示a[i]的第j个因子对应的倍数有a[i]}
}//输出最大因子对应的三个倍数
for(int i=1e5;i>=1;i--)
{if(s[i].size()>=3){cout<<s[i][0]<<" "<<s[i][1]<<" "<<s[i][2];return 0;}
}return 0;
}
6.爬山
考点:优先队列(堆)
贪心思想:
每次对最大的那个数字操作-->大根堆(也就是优先队列的默认)
开根号比除以2减小的更快
代码:
//考点是优先队列(默认大根堆)
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,p,q;
cin>>n>>p>>q;
priority_queue<int,vector<int> >d;//vector<int>后面需要有个空格
int h;
for(int i=0;i<n;i++){cin>>h;d.push(h);
}
for(int i=0;i<p;i++){int a=d.top();a=sqrt(a);d.pop();d.push(a);
}
for(int i=0;i<q;i++){int a=d.top();a/=2;d.pop();d.push(a);
}
long long int sum=0;
for(int i=0;i<n;i++){sum+=d.top();d.pop();
}
cout<<sum;
return 0;
}
堆总结
c++里堆用priority_queue写
堆操作:
默认是大根堆,堆顶最大
大根堆写法:
priority_queue<int,vector<int> >
小根堆:
priority_queue<int,vector<int>,greater<int> >q;
自定义堆:
用结构体struct自定义堆
例如:下面自定义的小根堆
//自定义堆
#include<bits/stdc++.h>
using namespace std;
struct Com{bool operator()(int a,int b){return a>b;}
};
int main(){int n;cin>>n;priority_queue<int,vector<int>,Com >q;for(int i=0;i<n;i++){int a;cin>>a;q.push(a);}return 0;
}
说明这个结构体定义的是小根堆,数字越小优先级越高
8.拔河(非常难做)
暴力(四种循环,20%)
#include <iostream>
using namespace std;
long long int a[1003];
long long int ans;
long long int s(int l,int r){
long long int sum=0;for(int i=l;i<=r;i++){sum+=a[i];}return sum;
}
int main()
{int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];ans+=a[i];}for(int l1=1;l1<n;l1++){for(int r1=l1;r1<n;r1++){for(int l2=r1+1;l2<=n;l2++){for(int r2=l2;r2<=n;r2++){long long int z=s(l1,r1);long long int y= s(l2,r2);ans=min(ans,abs(z-y));}}}}cout<<ans;return 0;
}
正解: 前缀和+multiset+lowerbound(100%)
完整的代码:
/*
分析:
考察:
1.区间和-->前缀和算法
2.最接近-->二分算法(可以用lowerbound解决),所以可以用multiset存储区间和,multiset自带lower_bound
multiset是可含重复元素的集合,并且默认升序排列,可以使用lowerbound,upper_bound
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
const int N=1e3+6;
ll a[N];//原数列
ll s[N];//从头开始,前缀和
multiset<ll>m;//存区间和
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){cin>>a[i];
}
//前缀和
for(int i=1;i<=n;i++){s[i]=s[i-1]+a[i];
}
// //区间和(可以求得所有区间和)
// for(int i=1;i<=n;i++){
// for(int j=1;j<=i;j++){
// //计算区间[j,i]的和
// ll h=s[i]-s[j-1];
// m.insert(h);
// }
// }
ll ans=1e9;//先放最大
//求解答案(做减法)
for(int l=1;l<=n;l++){
for(int j=1;j<l;j++){//第一个区间用multiset维护m.insert(s[l-1]-s[j-1]);
}
for(int r=l;r<=n;r++){//sum为区间[l,r]的和(第二个区间)ll sum=s[r]-s[l-1];//取出一个区间//利用lowerbound找与他最接近的区间和auto t=m.lower_bound(sum);//lowerbound返回第一个大于等于他的数的迭代器if(t!=m.end()){//表示存在大于等于他的数
ans=min(ans,abs(*t-sum));}if(t!=m.begin()){//比他小的t--;ans=min(ans,abs(*t-sum));}
}
}
cout<<ans;return 0;
}
核心代码解释:
(1)碰见区间求和要想到用前缀和的办法
前缀和模版
//前缀和(即区间[1,i]和)
for(int i=1;i<=n;i++){
s[i]=s[i-1]+a[i];
}
(2)两个区间和计算,找最小差值
ll ans=1e9;//先放最大
//求解答案(做减法)
for(int l=1;l<=n;l++){
for(int j=1;j<l;j++){//第一个区间用multiset维护
m.insert(s[l-1]-s[j-1]);
}
for(int r=l;r<=n;r++){
//sum为区间[l,r]的和(第二个区间)
ll sum=s[r]-s[l-1];//取出一个区间
//利用lowerbound找与他最接近的区间和
auto t=m.lower_bound(sum);//lowerbound返回第一个大于等于他的数的迭代器
if(t!=m.end()){//表示存在大于等于他的数
ans=min(ans,abs(*t-sum));
}
if(t!=m.begin()){//比他小的
t--;
ans=min(ans,abs(*t-sum));
}
}
}
cout<<ans;
lowerbound在这道题的作用:
先取出一个右边区间的和,然后在左边找一个最接近他的区间和,左边各区间和用multiset维护,找寻第一个大于等于他的数用lowerbound,返回迭代器。
然后也可能左边区间和比他小的那个作差绝对值更小,所以用迭代器向前一位的区间和算一下试试。
删除时如果用数字删会删全部,如果用迭代器删只删除指定位置
7.数字接龙
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int a[N][N];//存放图
string path;//存路径
bool st[N][N];//存这个点是否经过
bool edge[N][N][N][N];//edge[i][j][x][y]表示起点(i,j)终点(x,y)的斜线是否经过 处理交叉问题
int n,k;
//方向向量
int dx[]={-1,-1,0,1,1,1,0,-1};//0-7 行
int dy[]={0,1,1,1,0,-1,-1,-1};//列
bool dfs(int x,int y){if(x==n-1&&y==n-1){//搜到终点 return path.size()==n*n-1;//如果恰好经过n*n-1步到达终点,说明方案可行的 }st[x][y]=true;//经过当前点for(int i=0;i<8;i++) {int bx=x+dx[i];int by=y+dy[i];if(bx>=n||bx<0||by>=n||by<0){//越界continue; }if(st[bx][by]){continue;//经过了(防止重复走) }if(i%2&&(edge[bx][y][x][by]||edge[x][by][bx][y])){//只有斜线才防止交叉即1,3,5,7方向 continue; }if(a[bx][by]!=(a[x][y]+1)%k){//不满足走的要求continue;}edge[x][y][bx][by]=true;//恢复path+=i+'0';if(dfs(bx,by)){//注意只要找到了就可退出!!return true;}edge[x][y][bx][by]=false;path.pop_back();}st[x][y]=false;//恢复现场return false;//不存在路径
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){for(int j=0;j<n;j++){cin>>a[i][j];}
}
if(!dfs(0,0)){//从起点(0,0) 开始搜没有搜到
cout<<"-1" ;
}
else{cout<<path;
}
return 0;
}
交叉:
双方向x,by->bx,y和bx,y->x,by如果存在 就交叉会和斜线方向