2023蓝桥杯省赛B组真题及解析

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;

法二暴力
 //暴力 直接枚举 注意要想能运行需要注意剪枝 把不符合条件及早排除
#include<bits/stdc++.h> 
using namespace std;
int num[100];
bool flag[20][40];
int ans;
bool check(int month,int ri){
if(month==1||month==3||month==5||month==7||month==8||month==10||month==12){
	if(ri>=1&&ri<=31)return true;
}
if(month==4||month==6||month==9||month==11){
	if(ri>=1&&ri<=30)return true;
}
if(month==2){
	if(ri>=1&&ri<=28){
		return true;
	}
}
return false;
}
int main(){
	for(int i=0;i<100;i++){
		cin>>num[i];
	}
	for(int a=0;a<100;a++){
		if(num[a]!=2)continue;
		for(int b=a+1;b<100;b++){
			if(num[b]!=0)continue;
			for(int c=b+1;c<100;c++){
				if(num[c]!=2)continue;
				for(int d=c+1;d<100;d++){
					if(num[d]!=3)continue;
					for(int e=d+1;e<100;e++){
						if(num[e]>1)continue;
						for(int f=e+1;f<100;f++){
							if(num[e]*10+num[f]>12||num[e]*10+num[f]<1)continue;
						for(int g=f+1;g<100;g++){
							for(int h=g+1;h<100;h++){
								int year=num[a]*1000+100*num[b]+10*num[c]+num[d];
								int month=10*num[e]+num[f];
								int ri=10*num[g]+num[h];
							//	cout<<year<<"/"<<month<<"/"<<ri<<endl;
//								if(year!=2023){//不是2023年 
//									continue;
//								}
								if(!check(month,ri)) {//日期不合法 
									continue;
								}
								if(flag[month][ri]){//已经有了这一天 
									continue;
								}
								flag[month][ri]=true;
								ans++;
							}
						}	
						}
					}
				}
			}
		}
	}
	cout<<ans;
	return 0;
}
高效的方法解决
 //高效做法 遍历2023年日期看字符串里有没有满足的!
#include<bits/stdc++.h>
using namespace std;
int a[100];
int d[] ={0,31,28,31,30,31,30,31,31,30,31,30,31};
int ans;
int main(){
	for(int i=0;i<100;i++){
		cin>>a[i];
	}
	for(int i=1;i<=12;i++){//月 
		for(int j=1;j<=d[i];j++) {
			int date[]={2,0,2,3,i/10,i%10,j/10,j%10};//日期 
			int w=0;
			for(int k=0;k<100;k++){//去a数组找
			if(w==8){
				ans++;
				break;
			}
			 if(date[w]==a[k]){
			 	w++;
			 }
			 
				
			}
		}
	}
	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;
}

 

posted @   Annaprincess  阅读(388)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示