2023蓝桥杯省赛B组真题及解析
7.子串简写
算法:前缀和
https://www.lanqiao.cn/problems/3514/learning/?subject_code=1&group_code=4&match_num=14&match_flow=1&origin=cup
#include<bits/stdc++.h> using namespace std; int main() { int K; cin>>K; string s; cin>>s; char c1,c2; cin>>c1>>c2; long long nums=0,ans=0;//nums为此点前面c1的个数的前缀和,ans为答案 for(int i=K-1,j=0;i<s.length();i++,j++)//i右边界,j为左边界,时刻维持区间长度为k { if(s[j]==c1) nums++;//j点是c1 //j指向的是是否有与c1匹配的字符串,如果有将nums加1,即使后面的是s[i]不等于c2, //由于nums已经加1,如果后续有s[i]与c2相匹配的话,就将ans+上nums, //因为i是由K-1开始的,可以画个图。 if(s[i]==c2) ans+=nums;//nums存放了以i做右边界满足条件c1的个数。 } cout<<ans; return 0; }1.日期统计
https://www.lanqiao.cn/problems/3492/learning/?subject_code=1&group_code=4&match_num=14&match_flow=1&origin=cup
算法:dfs剪枝
//找不同时间(找数据)用搜索
#include <iostream>
using namespace std;
int a[100];
bool vis[20240000];
int ans ;
bool check(int date){
if(vis[date])return false;
//如果没访问就访问
vis[date]=true;
//前四位2023满足条件,只需考察月份和日子
int mm=date/100%100;
int rr=date%100;
if(mm<1||mm>12) return false;
if(mm==1||mm==3||mm==5||mm==7||mm==8|mm==10||mm==12){
if(rr>=1&&rr<=31)return true;
return false;
}
if(mm==4||mm==6||mm==9||mm==11){
if(rr>=1&&rr<=30)return true;
return false;
}
if(mm==2){
if(rr>=1&&rr<=28)return true;
}
}
void dfs(int x,int pos,int date)//x表示遍历到a数组中的哪一位,pos表示遍历到8位时间的哪一位
//date为所得时间,所得八位时间用八位数表示如:20230101
{
if(x==100)return ;//题目中100个数字遍历完
if(pos==8) {
//如果8位时间遍历完
if(check(date)) ans++;//找到一个八位数
return ;//递归函数出口
}
//当前a[x]选入date
//前四位有要求2023,后四位月份和日每个都有要求
if(pos==0&&a[x]==2||pos==1&&a[x]==0||pos==2&&a[x]==2||pos==3&&a[x]==3||
pos==4&&a[x]>=0&&a[x]<=1||pos==5&&a[x]>=0&&a[x]<=9||pos==6&&a[x]>=0&&a[x]<=3||
pos==7&&a[x]>=0&&a[x]<=9
){
dfs(x+1,pos+1,date*10+a[x]);//dfs(下一个数,日期中的下一位,日期转化成八位数)
}
//当前a[x]不放入date中 ,也就是date不进行到下一位,而x往后一位
dfs(x+1,pos,date);//dfs(下一个数,日期中的当前位,当前已经选的数字形成部分日期所组成的数 )
}
int main()
{
for(int i=0;i<100;i++)cin>>a[i];
dfs(0,0,0);
cout<<ans;
// 请在此输入您的代码
return 0;
}
3.冶炼金属
https://www.lanqiao.cn/problems/3510/learning/?subject_code=1&group_code=4&match_num=14&match_flow=1&origin=cup
考点是找到公式
(a/(b+1))+1=<V<=a/b
#include <iostream> using namespace std; const int N=1e4+6; int a[N],b[N]; int main() { int n; cin>>n; int m=1e9+6; int s=0; for(int i=0;i<n;i++){ cin>>a[i]>>b[i]; m=min(m,a[i]/b[i]);//Vmax s=max(s,(a[i]/(1+b[i]))+1);//Vmin } cout<<s<<" "<<m; // 请在此输入您的代码 return 0; }
2.01的熵
考点:浮点数精度问题
#include<bits/stdc++.h> using namespace std; typedef long double db; db a=11625907.5798; const int N=23333333; //注意浮点数有精度问题要用绝对值误差 const db p=1e-4; int main(){ for(int i=0;i<=N/2;i++){ int u=i;//0的个数 int v=N-i;//1的个数 db p0=(1.0)*u/N; db p1=1.0*v/N;//因为要防止整数相除的陷阱所以用1.0改成double型,每一个除法都改! db sum=(-1.0)*u*p0*log2(p0)+(-1.0)*v*p1*log2(p1); if(fabs(sum-a)<=p){ cout<<u; return 0; } } return 0; }4.飞机降落
dfs
#include<bits/stdc++.h> using namespace std; const int N=1e5+8; int t[N],d[N],l[N]; int m;//本组m架飞机 bool have_ans=true; bool st[N]; void dfs(int a,int b) { //已降落m架说明降落方案可行 if(a==m){ have_ans=true; return ; } for(int i=1;i<=m;i++){ //最晚开始降落时间t[i]+d[i] if(!st[i]&&b<=t[i]+d[i]){//降落第i架 st[i] =true; //最早是t[i]+l[i]所以可能不是时间b+l[i]出发,可能要等一会儿 dfs(a+1,max(b,t[i])+l[i]); st[i]=false;//恢复 } } //最终如果还没有达到a==m,会自动返回并且have_ans==false } void solve(){ have_ans=false;//默认没答案 cin>>m; memset(st, 0, sizeof st); //恢复所有元素为没被访问,必须要 for(int i=1;i<=m;i++)cin>>t[i]>>d[i]>>l[i]; dfs(0,0);//运用dfs对所有情况进行搜索,dfs(i,j)已经降落 i架飞机,已经用时 j分钟 if(have_ans) cout<<"YES"<<endl;//有答案 else cout<<"NO" <<endl;//无答案 } int main(){ int m; cin>>m; while(m--){ solve();//每一次解决 } return 0; }5.接龙数列
动态规划
#include<bits/stdc++.h> using namespace std; int dp[10];//以0~9结尾每种情况的个数 int main(){ int n; cin>>n; string s; int m=0; for(int i=0;i<n;i++){ cin>>s; int p=s[0]-'0';//最高位 int q=s[s.size()-1]-'0';//最低位 dp[q]=max(dp[q],dp[p]+1);//更新当前数字最低位结尾的数字个数就是max(以当前结尾的数字个数,连接上当前数的个数) m=max(m,dp[q]);//最长接龙数列 } cout<<n-m;//删除个数 return 0; }6.岛屿个数
考点:BFS
https://www.lanqiao.cn/problems/3513/learning/?subject_code=1&group_code=4&match_num=14&match_flow=1&origin=cup
(有点难)
#include<bits/stdc++.h> using namespace std; const int N=60; bool vis[N][N];//在判断岛屿时是否被搜索过 bool used[N][N];//在判断是否为子岛屿是否被使用过 char ma[N][N];//地图 int m,n; int dx[8]={-1,0,0,1,-1,-1,1,1}; int dy[8]={0,-1,1,0,-1,1,-1,1}; //判断岛屿 void bfs_island(int a,int b){ queue<int>qx; queue<int>qy; qx.push(a); qy.push(b); vis[a][b]=true;//这个点被访问 while(qx.size()){ int x=qx.front();qx.pop(); int y=qy.front();qy.pop(); for(int i=0;i<4;i++){ int nx=x+dx[i]; int ny=y+dy[i]; if(nx<0||ny<0||nx>m-1||ny>n-1||vis[nx][ny]==true||ma[nx][ny]=='0')continue; qx.push(nx);qy.push(ny); vis[nx][ny]=true; } } } //是否成环-->能否逃出去 bool bfs_out(int a,int b){ memset(used,false,sizeof(used)); queue<int>qx; queue<int>qy; qx.push(a); qy.push(b); used[a][b]=true; while(qx.size()){ int x=qx.front();qx.pop(); int y=qy.front();qy.pop(); //已经找着跳出边界 if(x==0||y==0||x==m-1||y==n-1){ return true;//判断不是子岛屿就要ans++ } for(int i=0;i<8;i++){ int nx=x+dx[i]; int ny=y+dy[i];if(nx<0||ny<0||nx>m-1||ny>n-1||used[nx][ny]==true||ma[nx][ny]=='1')continue;//不能逃的情况 qx.push(nx); qy.push(ny); used[nx][ny]=true; } } return false; } void solve(){ int ans=0; cin>>m>>n; //输入地图 for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ cin>>ma[i][j]; vis[i][j]=false;//初始化先所有格子未访问 } } //进行两次bfs,计算岛屿个数 for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ //计算未访问的岛屿 if(ma[i][j]=='1'&&!vis[i][j]){ bfs_island(i,j);//将相邻1组成的岛屿搜索出来 if(bfs_out(i,j))ans++;//判断非子岛屿 } } } cout<<ans<<endl; } int main(){ int t; cin>>t;//t组 while(t--){ solve(); } return 0; }
8.整数删除
考点:优先队列+链表
优先队列:每次方便取最值
链表:此处用的是静态链表且无头结点。方便删除。-->静态链表的删除结点不是真删除而是修改值让这个值不在范围内。
此处的链表为双向链表,开两个数组存放前驱后继下标。
#include<bits/stdc++.h>//静态链表 using namespace std; int n,k;//长度为n的数列操作k次 const int N=5e5+6; long long int A[N];//存放A数列 int pre[N];//前驱下标数组 int nex[N];//后继下标数组 typedef pair<long long int ,int>PII; //优先队列 priority_queue<PII,vector<PII> >q; int main(){ cin>>n>>k; for(int i=1;i<=n;i++){ cin>>A[i]; //小根堆,优先按存放值小的优先,然后是存放下标(即second)小的优先 q.push({-A[i],-i});//前面为数列值,后面是下标,小根堆可以用负数放进去(优先队列) //双向链表 pre[i]=i-1; nex[i]=i+1; } //特殊情况的前驱后继两头 pre[1]=-1; nex[n]=-1; //k次操作 while(k--){ PII now; //选择最小结点 //循环的目的,因为每一次要找的结点必须是修改后A中的所以要做检验,位置和值是否对应 do{ now=q.top(); q.pop(); //选最小 now.first=-now.first; now.second=-now.second; }while(A[now.second]!=now.first); int PRE=pre[now.second]; int NXT =nex[now.second]; //前驱存在 if(PRE!=-1){ A[PRE]+=now.first; nex[PRE]=NXT; q.push({-A[PRE],-PRE}); } //后继存在 if(NXT!=-1){ A[NXT]+=now.first; pre[NXT]=PRE; q.push({-A[NXT],-NXT}); } //删除当前结点 A[now.second]=-1; } for(int i=1;i<=n;i++){ if(A[i]!=-1)cout<<A[i]<<" "; } return 0;
}
9.景区导游
考点: 图论 LCA(公共祖先问题)+dfs
题目分析:
n个景点对应树上n个结点
n-1条摆渡车对应n-1条树上的边
建树
考点:
树上任意两点的距离path(a,b)=dis[a]+dis[b]-2*dis[lca(a,b)];-->所以手写公共祖先->lca以及配合的dfs->dfs里面在模版基础上增加求到根节点距离
完整代码解析:
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; //邻接表 vector<int>e[N];//邻接点 vector<int>t[N];//消耗时间 int dep[N];//深度 int fa[N][21];//注意题目中10的五次方所以第二维要开到21因为最大2的20次方即[ ] [20] long long int ans;//存答案可能超int int n,k;//n个景点,选中的游览路线里有k个景点 int A[N];//所选路线 long long int dis[N];//每个顶点到根节点的距离即时间,可能超int//LCA
void dfs(int u,int Fa){ dep[u]=dep[Fa]+1; fa[u][0]=Fa; for(int i=1;i<=20;i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(int i=0;i<e[u].size();i++){ int v=e[u][i];int p=t[u][i]; if(v==Fa)continue; dis[v]=dis[u]+p;//每个节点都有唯一的父亲,所以用父亲算子节点到根节点的距离,即根节点到u的子节点v的距离等于根节点到u的距离+u与v之间的距离 dfs(v,u);//递归 } } int LCA(int u,int v){ if(dep[u]<dep[v])swap(u,v); for(int i=20;i>=0;i--){ if(dep[fa[u][i]]>=dep[v]){ u=fa[u][i]; } } if(u==v)return u; for(int i=20;i>=0;i--){ if(fa[u][i]!=fa[v][i]){ u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; } //求相邻两点距离 long long int path(int u,int v){ if(u==0||v==0){ return 0;//看绿下标为0的那种不存在的点,到他的距离为0 } return dis[u]+dis[v]-2*dis[LCA(u,v)]; } int main(){ cin>>n>>k; //n-1条摆渡车,邻接表 for(int i=1;i<n;i++){ int u,v,w; cin>>u>>v>>w; e[u].push_back(v); t[u].push_back(w); e[v].push_back(u); t[v].push_back(w); } //为lca做准备,并求每个所问结点到根节点的距离(时间) dfs(1,0); //所问线路 for(int i=1;i<=k;i++){ cin>>A[i];//存放所问线路 ans+=path(A[i-1],A[i]);//先存放删除前总时间,即相邻点的距离 } //删除第i个结点 for(int i=1;i<=k;i++){ cout<<ans-path(A[i-1],A[i])-path(A[i],A[i+1])+path(A[i-1],A[i+1])<<" "; } return 0; } 删除一点: 距离和变化: ans-path(A[i-1],A[i])-path(A[i],A[i+1])+path(A[i-1],A[i+1])完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
//邻接表
vector<int>e[N];//邻接点
vector<int>t[N];//消耗时间
int dep[N];//深度
int fa[N][21];
long long int ans;//存答案可能超int
int n,k;//n个景点,选中的游览路线里有k个景点
int A[N];//所选路线
long long int dis[N];//每个顶点到根节点的距离即时间
void dfs(int u,int Fa){
dep[u]=dep[Fa]+1;
fa[u][0]=Fa;
for(int i=1;i<=20;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=0;i<e[u].size();i++){
int v=e[u][i];int p=t[u][i];
if(v==Fa)continue;
dis[v]=dis[u]+p;//每个节点都有唯一的父亲,所以用父亲算子节点到根节点的距离
dfs(v,u);
}
}
int LCA(int u,int v){
if(dep[u]<dep[v])swap(u,v);
for(int i=20;i>=0;i--){
if(dep[fa[u][i]]>=dep[v]){
u=fa[u][i];
}
}
if(u==v)return u;
for(int i=20;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
//求相邻两点距离
long long int path(int u,int v){
if(u==0||v==0){
return 0;//看绿下标为0的那种不存在的点,到他的距离为0
}
return dis[u]+dis[v]-2*dis[LCA(u,v)];
}
int main(){
cin>>n>>k;
//n-1条摆渡车,邻接表
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
e[u].push_back(v);
t[u].push_back(w);
e[v].push_back(u);
t[v].push_back(w);
}
//为lca做准备,并求每个所问结点到根节点的距离(时间)
dfs(1,0);
//所问线路
for(int i=1;i<=k;i++){
cin>>A[i];
ans+=path(A[i-1],A[i]);//先存放删除前总时间,即相邻点的距离
}
//删除第i个结点
for(int i=1;i<=k;i++){
cout<<ans-path(A[i-1],A[i])-path(A[i],A[i+1])+path(A[i-1],A[i+1])<<" ";
}
return 0;
}
10.砍树
题面解析: n个顶点 n-1条已经给了的边 由以上两个条件画树 问: 给出m个组合要求每个组合里两个顶点不相通! #include <bits/stdc++.h> using namespace std; const int N=1e5+5; vector<int>e[N],num[N]; int n,m,dep[N],s[N],fa[N][21]; int ans=-1;//核心找u,v公共祖先LCA算法
void dfs(int u,int Fa){ dep[u]=dep[Fa]+1; fa[u][0]=Fa; for(int i=1;i<=20;i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(auto&v:e[u]){ if(v==Fa)continue; dfs(v,u); } } int LCA(int u,int v){ if(dep[u]<dep[v])swap(u,v); for(int i=20;i>=0;i--){ if(dep[fa[u][i]]>=dep[v]){ u=fa[u][i]; } } if(u==v)return u; for(int i=20;i>=0;i--){ if(fa[u][i]!=fa[v][i]){ u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; } void dfs2(int u,int Fa){ for(int i=0;i<e[u].size();i++){ int v=e[u][i],p=num[u][i]; if(v==Fa)continue; dfs2(v,u); s[u]+=s[v]; if(s[v]==m)ans=max(ans,p); } } //主函数 int main() { cin>>n>>m; for(int i=1;i<n;i++){ int x,y; cin>>x>>y; e[x].push_back(y); num[x].push_back(i); e[y].push_back(x); num[y].push_back(i); } dfs(1,0); for(int i=1;i<=m;i++){ int a,b; cin>>a>>b; s[a]++; s[b]++; s[LCA(a,b)]-=2; } dfs2(1,0); cout<<ans; // 请在此输入您的代码 return 0; }分段分析代码
LCA
查看代码
//LCA算法包含两部分:先dfs在求公共祖先
//所需数组:深度dep[ ],找向上的下标fa[当前结点][向上2的多少次方级],邻接表存边 e[u]表示与u相邻的边的另一个点
void dfs(int u,int Fa){//u是当前点,Fa是他父亲的下标
dep[u]=dep[Fa]+1;//他自己的深度是他父亲深度加1
fa[u][0]=Fa;//u的向上2的0次方级是他父亲
//从下往上找祖先
for(int i=1;i<=20;i++){//向上最多2的20方级,因为题目给最多10的5次方个结点。
fa[u][i]=fa[fa[u][i-1]][i-1];//递归:u向上第2的i次方级等价于u向上2的i-1次方所得元素再向上2的i-1次方即2的i-1次方乘2=2的i次方。
}
for(auto&v:e[u]){//当前所给顶点u的所有邻结点
if(v==Fa)continue;
//相邻的这个点不是它父亲再dfs递归,
dfs(v,u);//因为是从最上面的点往下搜,不能重复搜它上面的点,所以他父亲不能再搜。
}
}
//返回值就是u,v的公共祖先
int LCA(int u,int v){//上面dfs是准备工作,下面是核心LCA找u,v公共祖先下标
if(dep[u]<dep[v])swap(u,v);//保证u更深
for(int i=20;i>=0;i--){//u大步向上跳跃
if(dep[fa[u][i]]>=dep[v]){//u的向上2的i次方级的深度大于v的深度
u=fa[u][i];//那么修改u下标为它向上2的i次方级下标
}
}
//所以出循环后u的深度>=v
if(u==v)return u;//u跳完与v相遇那么返回u,说明原来v是u的直系祖先
for(int i=20;i>=0;i--){//两人一起往上大步跳直至祖先相同
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];//最终修改的u,v有共同的父亲就是原u,v的公共祖先
}
砍树,利用dfs看砍哪个边
void dfs2(int u,int Fa){//dfs2(当前节点下标,父节点下标)
for(int i=0;i<e[u].size();i++){//u的所有邻接点
int v=e[u][i],p=num[u][i];//v是此时与u相邻的点,p是u此时所在边的编号
if(v==Fa)continue;//不重复搜父亲
dfs2(v,u);
s[u]+=s[v];
if(s[v]==m)ans=max(ans,p);//p是关键边
}
}
主函数
int main()
{
cin>>n>>m;//n个顶点,m组组合不相通
for(int i=1;i<n;i++){//n-1条已知边,等会砍的边只能从这里面选
int x,y;
cin>>x>>y;//一条边的两个顶点
//8~11为邻接表存边,因为是按无向图存所以x,y各弄一次
e[x].push_back(y);//x当前的邻接点y
num[x].push_back(i);//并存放x当前所在边边的标号,因为题目求满足条件的最大边标号
e[y].push_back(x);//同理
num[y].push_back(i);
}
dfs(1,0);//先进行LCA的预备工作dfs利用深搜处理每个点的祖先,从第一个结点开始搜他的父节点默认0.
for(int i=1;i<=m;i++){//核心代码:考察树上差分算法
int a,b;
cin>>a>>b;
s[a]++;
s[b]++;
s[LCA(a,b)]-=2;
}
dfs2(1,0);//从第一个结点开始搜他的父节点默认0.
cout<<ans;//输出答案
// 请在此输入您的代码
return 0;
}
主函数里的核心算法树上差分
树上差分
for(int i=1;i<=m;i++){//核心代码:考察树上差分算法
int a,b;
cin>>a>>b;
s[a]++;
s[b]++;
s[LCA(a,b)]-=2;
}
/*解析:
因为要使得这m组组合不相通
所以先标记一下原来相通时状态,s[a]表示a到其父亲必须右边,而他们公共祖先往上不一定要边所以要减去2因为a,b各贡献+1
*/
//这个搜索是配合树上差分
void dfs2(int u,int Fa){//dfs2(当前节点下标,父节点下标)
for(int i=0;i<e[u].size();i++){//u的所有邻接点
int v=e[u][i],p=num[u][i];//v是此时与u相邻的点,p是u此时所在边的编号
if(v==Fa)continue;//不重复搜父亲
dfs2(v,u);
s[u]+=s[v];
if(s[v]==m)ans=max(ans,p);//p是关键边
}
}
//s[v]表示v到其父亲的这条路径在题目中作为关键路径所需要走的次数
例如上图,这两个点之间的有关键路径就对该路径+1,其实就是s[]变化!
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int>e[N];//vector数组的数组,其实就是邻接矩阵存边,例如e[u]表示与u相邻的点
vector<int>num[N];//边标号
int dep[N];//各结点深度
int fa[N][21];//注意他的第二位是向上2的多少次方级最多21表示2的21次方级,不能开N太大会爆!
int n,m;//n个顶点m种不相通组合
int ans=-1;//没有可行方案默认-1
int s[N];
void dfs(int u,int Fa){
dep[u]=dep[Fa]+1;
fa[u][0]=Fa;
//从下往上找
for(int i=1;i<=20;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto &v:e[u]){
if(v==Fa)continue;
dfs(v,u);
}
}
int LCA(int u,int v){//返回u,v的公共祖先下标
//保证u的深度更深
if(dep[u]<dep[v])swap(u,v);
//u大步向上跳
for(int i=20;i>=0;i--){
if(dep[fa[u][i]]>=dep[v]){
u=fa[u][i];
}
}
//如果u,v相遇
if(u==v){
return u;
}
//否则u,v一起大步向上跳
for(int i=20;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
//u,v祖先不同就一起往上跳
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];//原u,v公共祖先是修改后u,v的公共父亲
}
//配合树上差分
void dfs2(int u,int Fa){//当前节点下标u,其父节点下标Fa
for(int i=0;i<e[u].size();i++){
int v=e[u][i];int p=num[u][i];
if(v==Fa)continue;
dfs2(v,u);
s[u]+=s[v];
if(s[v]==m){//所给这m种组合都要过。
ans=max(ans,p);
}
}
}
int main(){
cin>>n>>m;
//邻接表存边((n-1条))
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
e[x].push_back(y);
num[x].push_back(i);
e[y].push_back(x);
num[y].push_back(i);
}
//为LCA做准备
dfs(1,0);
//m种不相通组合,树上差分:
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
s[x]++;
s[y]++;
s[LCA(x,y)]-=2;
}
//砍树
dfs2(1,0);
cout<<ans;//输出答案
return 0;
}
标签:dep,组真题,return,int,dfs,蓝桥,fa,push,省赛 From: https://www.cnblogs.com/luckyhappyyaoyao/p/18005879