第14届蓝桥杯C++B组省赛题解(A-J)(更新完毕)

yizhihongxing

A. 日期统计

题目内容

小蓝现在有一个长度为 \(100\) 的数组,数组中的每个元素的值都在 \(0\)\(9\) 的范围之内。
数组中的元素从左至右如下所示:

5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7
0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 3 8 5 1 6 3 4 6 7 0 7 8 2 7 6 8 9 5 6 5 6 1 4 0 1
0 0 9 4 8 0 9 1 2 8 5 0 2 5 3 3

现在他想要从这个数组中寻找一些满足以下条件的子序列:

  1. 子序列的长度为 \(8\)
  2. 这个子序列可以按照下标顺序组成一个 \(yyyymmdd\) 格式的日期,并且
    要求这个日期是 \(2023\) 年中的某一天的日期,例如 \(20230902,20231223\)
    \(yyyy\) 表示年份,\(mm\) 表示月份,\(dd\) 表示天数,当月份或者天数的长度只有一位时需要一个前导零补充。
    请你帮小蓝计算下按上述条件一共能找到多少个不同的 \(2023\) 年的日期。
    对于相同的日期你只需要统计一次即可。
    本题的结果为一个整数,在提交答案时只输出这个整数,输出多余的内容将无法得分。

思路

八重循环枚举日期+set去重即可
\(Tips:\) 前4重特判2023,否则程序会跑的很慢

代码

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n=100;
int num[N];
set<string>s;

bool check(string all)
{
	int m=stoi(all.substr(0,2));
	int d=stoi(all.substr(2,2));
	
	int mon[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
	if(m>=1&&m<=12)
	{
		if(d>=1&&d<=mon[m])
			return true;
	}
	
	return false;
}
int main()
{
	for(int i=0;i<n;i++)cin>>num[i];



	for(int a=0;a<n;a++)
	{
	
		if(num[a]!=2)continue;
		for(int b=a+1;b<n;b++)
		{
			if(num[b]!=0)continue;
			for(int c=b+1;c<n;c++)
			{
				if(num[c]!=2)continue;
				for(int d=c+1;d<n;d++)
				{	
					if(num[d]!=3)continue;
					for(int e=d+1;e<n;e++)
					{
						for(int f=e+1;f<n;f++)
						{
							for(int g=f+1;g<n;g++)
							{
								for(int h=g+1;h<n;h++)
								{
									string n1=to_string(num[e]);
									string n2=to_string(num[f]);
									string n3=to_string(num[g]);
									string n4=to_string(num[h]);
									string all=n1+n2+n3+n4;
									if(check(all))s.insert(all);
									
									
								}
							}
						}
					}
				}
			}
		}
	}	
	
	
	cout<<s.size()<<endl;
	
	return 0;
}

答案

235

B.01 串的熵

题目内容

对于一个长度 $ n $ 的 \(01\)\(S = x_1x_2x_3...x_n\)
香农信息熵的定义为:\(H(S)=-\sum_{i=1}^{n}p(x_i)log_2(p(x_i))\) ,其中 \(p(0)\), \(p(1)\) 表示在这个 \(01\) 串中 \(0\)\(1\) 出现的占比。
比如,对于 \(S = 100\) 来说,信息熵 \(H(S) = - \frac{1}{3}log_2(\frac{1}{3}) - \frac{2}{3} log_2(\frac{2}{3}) = 1.3083\)
对于一个长度为 \(23333333\)\(01\) 串,如果其信息熵为 \(11625907.5798\) ,且 \(0\) 出现次数比 \(1\) 少,那么这个 \(01\) 串中 \(0\) 出现了多少次?
本题的结果为一个整数,在提交答案时只输出这个整数,输出多余的内容将无法得分。

思路

按题意模拟即可,由题意可得 \(H(S)\)的值只与 \(01\) 出现的次数有关,因为 \(0\) 出现次数比 \(1\) 少,所以可以从 \(\lfloor \frac{23333333}{2} \rfloor = 11666666\) 开始往下枚举0的个数,同时计算 \(p(0),p(1)\) 的占比,带入公式验证是否相等,注意设置误差范围去判断浮点数是否相等

代码

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-4;
//#define double long double
int len;

int main()
{
	int len=23333333;
	for(int i=len/2;i>=1;i--)
	{
		double px0=1.0*i/len,px1=1.0*(len-i)/len;
		double H=-(i*px0*log2(px0)+(len-i)*px1*log2(px1));
		if(fabs(H-11625907.5798)<=eps)
		{
			cout<<i<<endl;
			return 0;
		}
	}
	
	return 0;
}

答案

11027421

C.冶炼金属

题目内容

小蓝有一个神奇的炉子用于将普通金属 \(O\) 冶炼成为一种特殊金属 \(X\) 。这个炉子有一个称作转换率的属性 \(V\)\(V\) 是一个正整数,这意味着消耗 \(V\) 个普通金属 \(O\) 恰好可以冶炼出一个特殊金属 \(X\)。当普通金属 \(O\) 的数目不足 \(V\) 时,无法继续冶炼。现在给出了 \(N\) 条冶炼记录,每条记录中包含两个整数 \(A\)\(B\),这表示本次投入了 \(A\) 个普通金属\(O\),最终冶炼出了 \(B\) 个特殊金属 \(X\)。每条记录都是独立的,这意味着上一次没消耗完的普通金属 \(O\) 不会累加到下一次的冶炼当中。根据这 \(N\) 条冶炼记录,请你推测出转换率 \(V\) 的最小值和最大值分别可能是多少。题目保证评测数据不存在无解的情况。

输入格式

第一行一个整数 \(N\),表示冶炼记录的数目。
接下来输入 \(N\) 行,每行两个整数 \(A、B\) ,含义如题目所述。
对于 \(30\%\) 的评测用例,\(1 ≤ N ≤ 100\)
对于 \(60\%\) 的评测用例,\(1 ≤ N ≤ 1000\)
对于 \(100\%\) 的评测用例,\(1 ≤ N ≤ 10000,1 ≤ B ≤ A ≤ 1,000,000,000\)

输出格式

输出两个整数,分别表示 \(V\) 可能的最小值和最大值,中间用空格分开。

输入样例

3
75 3
53 2
59 2

输出样例

20 25

思路

求最小值和最大值问题,可以利用二分答案进行判断。

  • 求最小值

    • 假设最终答案为 \(S\)
      • 因为 \(S\) 的最优性,若要求答案 \(<S\),对于每组金属 \(A\) 至少有一个不能冶炼出 \(B\) 个特殊金属
      • 若答案可以 \(>S\),则一定存在一个属性 \(V\) ,使得每组金属 \(A\) 都能冶炼出对应的 \(B\) 个特殊金属,最优解就处于可行性的分界点上
  • 求最大值与上面同理

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int a[N],b[N];
int n;

bool check_min(int x)
{
	//如果某一组存在a[i]/x的值比实际B大,说明V可以继续增加
	for(int i=0;i<n;i++)
		if(a[i]/x>b[i])return false;
	
	return true;
}

bool check_max(int x)
{
	//如果某一组存在a[i]/x的值比实际B小,说明V可以继续减小
	for(int i=0;i<n;i++)
		if(a[i]/x<b[i])return false;
	
	return true;
}
int main()
{
	cin>>n;
	
	for(int i=0;i<n;i++)cin>>a[i]>>b[i];
	
	
	int l=0,r=1e9;
	
	//求最小值
	while(l<r)
	{
		int mid=l+r>>1;
		if(check_min(mid))r=mid;
		else l=mid+1;
	}
	
	cout<<l<<' ';
	
	
	l=0,r=1e9;
	
	//求最大值
	while(l<r)
	{
		int mid=l+r+1>>1;
		//
		if(check_max(mid))l=mid;
		else r=mid-1;
	}
	
	cout<<l<<endl;
	
	return 0;
}

D.飞机降落

题目内容

\(N\) 架飞机准备降落到某个只有一条跑道的机场,其中第 \(i\) 架飞机在 \(T_i\) 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 \(D_i\) 个单位时间。即它最早可以于 \(T_i\) 时刻开始降落,最晚可以于 \(T_i + D_i\) 时刻开始降落。降落过程需要 \(L_i\) 个单位时间。一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落。但是不能在前一架飞机完成降落前开始降落。请你判断 \(N\) 架飞机是否可以全部安全降落。

输入格式

输入包含多组数据。
第一行包含一个整数 \(T\) ,代表测试数据的组数。
对于每组数据,第一行包含一个整数 \(N\)
以下 \(N\) 行,每行包含三个整数:\(T_i,D_i\)\(L_i\)
对于 \(30\%\) 的数据,\(N ≤ 2\)
对于 \(100\%\) 的数据,\(1 ≤ T ≤ 10,1 ≤ N ≤ 10,0 ≤ T_i,D_i,L_i ≤ 100,000\)

输出格式

对于每组数据,输出 \(YES\) 或者 \(NO\),代表是否可以全部安全降落。

输入样例

2
3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20

输出样例

YES
NO

对于第一组数据:
安排第 \(3\) 架飞机于 \(0\) 时刻开始降落,\(20\) 时刻完成降落。
安排第 \(2\) 架飞机于 \(20\) 时刻开始降落,\(30\) 时刻完成降落。
安排第 \(1\) 架飞机于 \(30\) 时刻开始降落,\(40\) 时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。

思路

\(N\)最大只有\(10\),最多10组测试组数,可以暴搜枚举所有方案

  • 优化:若搜索到一种合法方案,剪枝一路返回即可,不需要继续搜索

代码

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int>PII;
const int N=12;
PII a[N];
int d[N];
bool st[N];
int n;

//代表枚举到第u层,当前飞机的降落结束时间为now
bool dfs(int u,int now)
{
	
	if(u>=n)
	{
		for(int i=0;i<n;i++)
			if(!st[i])return false;
		
		return true;
	}
	
	for(int i=0;i<n;i++)
	{
		if(!st[i])
		{
			st[i]=true;
			//如果当前飞机的最早降落时间小于等于now,并且最晚降落时间大于等于now,
			//则从当前时刻开始降落
			if(a[i].x<=now&&a[i].y>=now)
			{
				//降落结束时间now更新为now+d[i],继续枚举下一架飞机
				if(dfs(u+1,now+d[i]))return true;
			}
			//如果当前飞机的最早降落时间大于等于now
			else if(a[i].x>=now)
			{
				//降落结束时间now更新为a[i].x+d[i],继续枚举下一架飞机
				if(dfs(u+1,a[i].x+d[i]))return true;
			}
			st[i]=false;
		}
	}
	
	return false;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n;
		
		for(int i=0;i<n;i++)
		{
			cin>>a[i].x>>a[i].y>>d[i];
			a[i].y+=a[i].x;
		}

		memset(st,0,sizeof st);
		puts(dfs(0,0)?"YES":"NO");
	}
	
	return 0;
}

E.接龙数列

题目内容

对于一个长度为 \(K\) 的整数数列:\(A_1, A_2, ... , A_K\),我们称之为接龙数列:当且仅当 \(A_i\) 的首位数字恰好等于 \(A_{i−1}\) 的末位数字\((2 ≤ i ≤ K)\)。例如:\(12, 23, 35, 56, 61, 11\) 是接龙数列;\(12, 23, 34, 56\) 不是接龙数列,因为 \(56\) 的首位数字不等于 \(34\) 的末位数字。所有长度为 1 的整数数列都是接龙数列。现在给定一个长度为 \(N\) 的数列 \(A_1, A_2, ... , A_N\),请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?

输入格式

第一行包含一个整数 \(N\)
第二行包含 \(N\) 个整数 \(A_1, A_2, ... , A_N\)
对于 \(20\%\) 的数据,\(1 ≤ N ≤ 20\)
对于 \(50\%\) 的数据,\(1 ≤ N ≤ 10000\)
对于 \(100\%\) 的数据,\(1 ≤ N ≤ 10^5,1 ≤ A_i ≤ 10^9\)
所有 \(A_i\) 保证不包含前导 \(0\)

输出格式

一个整数代表答案。

输入样例

5
11 121 22 12 2023

输出样例

1

删除 \(22\),剩余 $ 11, 121, 12, 2023 $ 是接龙数列。

思路

线性 \(dp\),定义状态 \(f[i]\) , 代表以 \(i\) 结尾的最长序列的长度
因此,所求的最少删除数的个数 = $n - $最长接龙序列的长度

代码

#include<bits/stdc++.h>
using namespace std;
const int N=12;
int f[N];
int main()
{
	int n;
	cin>>n;
	
	int maxv=0;
	for(int i=0;i<n;i++)
	{
		string s;
		cin>>s;
		int a=s[0]-'0',b=s.back()-'0';
		f[b]=max(f[b],f[a]+1);//接到前面以a结尾的数后面;或者替换掉前面一个以b结尾的数,保持不变
		maxv=max(maxv,f[b]);//更新最大值
	}
	
	cout<<n-maxv<<endl;
	
	return 0;
}

F.岛屿数量

题目内容

小蓝得到了一副大小为 \(M × N\) 的格子地图,可以将其视作一个只包含字符\(0\)(代表海水)和 \(1\)(代表陆地)的二维数组,地图之外可以视作全部是海水,每个岛屿由在上/下/左/右四个方向上相邻的 \(1\) 相连接而形成。在岛屿 A 所占据的格子中,如果可以从中选出 \(k\) 个不同的格子,使得他们的坐标能够组成一个这样的排列:\((x_0, y_0),(x_1, y_1), . . . ,(x_{k−1}, y_{k−1})\),其中 \((\) \(x_{(i+1)\%k}, y_{(i+1)\%k}\) \()\) 是由 \((x_i, y_i)\) 通过上/下/左/右移动一次得来的 \((0 ≤ i ≤ k − 1)\),此时这 \(k\) 个格子就构成了一个 “环”。如果另一个岛屿 \(B\) 所占据的格子全部位于这个 “环” 内部,此时我们将岛屿 B 视作是岛屿 \(A\) 的子岛屿。若 \(B\)\(A\) 的子岛屿,\(C\) 又是 \(B\) 的子岛屿,那 \(C\) 也是 \(A\) 的子岛屿。请问这个地图上共有多少个岛屿?在进行统计时不需要统计子岛屿的数目。

输入格式

第一行一个整数 \(T\),表示有 \(T\) 组测试数据。
接下来输入 \(T\) 组数据。对于每组数据,第一行包含两个用空格分隔的整数 \(M、N\) 表示地图大小;接下来输入 \(M\) 行,每行包含 \(N\) 个字符,字符只可能是 \(0\)\(1\)

输出格式

对于每组数据,输出一行,包含一个整数表示答案。

输入样例

2
5 5
01111
11001
10101
10001
11111
5 6
111111
100001
010101
100001
111111

输出样例

1
3

对于第一组数据,包含两个岛屿,下面用不同的数字进行了区分:

01111
11001
10201
10001
11111

岛屿 \(2\) 在岛屿 \(1\) 的 “环” 内部,所以岛屿 \(2\) 是岛屿 \(1\) 的子岛屿,答案为 \(1\)
对于第二组数据,包含三个岛屿,下面用不同的数字进行了区分:

111111
100001
020301
100001
111111

注意岛屿 \(3\) 并不是岛屿 \(1\) 或者岛屿 \(2\) 的子岛屿,因为岛屿 \(1\) 和岛屿 \(2\) 中均没有“环”。
对于 \(30\%\) 的评测用例,\(1 ≤ M, N ≤ 10\)
对于 \(100\%\) 的评测用例,\(1 ≤ T ≤ 10,1 ≤ M, N ≤ 50\)

思路

两次宽搜,从 \((1,1)\) 处存图

  • 第一次宽搜,先从 \((0,0)\) ,即海水处开始向八个方向搜索,将能搜索到的 \(0\) 标记成 \(\#\),将每块岛屿分隔开
  • 第二次宽搜,遍历 \(g[][]\) 数组,当遇到 \(1\) 的时候,将 \(1\) 包围的整块区域标记成 \(\#\),同时要统计的岛屿个数加一

代码

#include<bits/stdc++.h>
#define x first 
#define y second
using namespace std;
typedef pair<int,int>PII;
const int N=55;
char g[N][N];
int n,m;
int dx[]={-1,0,1,0,-1,-1,1,1};
int dy[]={0,1,0,-1,-1,1,1,-1};

//分隔岛屿
void bfs1(int x,int y)
{
	queue<PII>q;
	
	q.push({0,0});
	g[0][0]='#';
	while(q.size())
	{
		auto t=q.front();
		q.pop();
		
		for(int i=0;i<8;i++)
		{
			int a=t.x+dx[i],b=t.y+dy[i];
			if(a<0||a>n+1||b<0||b>m+1||g[a][b]=='1'||g[a][b]=='#')continue;
			g[a][b]='#';
			q.push({a,b});
		}
	}
}

//将整块岛屿标记
void bfs2(int x,int y)
{
	queue<PII>q;
	
	q.push({x,y});
	g[x][y]='#';
	while(q.size())
	{
		auto t=q.front();
		q.pop();
		
		for(int i=0;i<4;i++)
		{
			int a=t.x+dx[i],b=t.y+dy[i];
			if(a<1||a>n||b<1||b>m||g[a][b]=='#')continue;
			g[a][b]='#';
			q.push({a,b});
		}
	}
}

int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		memset(g,'0',sizeof g);
		for(int i=1;i<=n;i++)cin>>g[i]+1;
		
		
		int x,y;
		bfs1(x,y);
			
		int cnt=0;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(g[i][j]=='1')
					bfs2(i,j),cnt++;//统计个数
			
		cout<<cnt<<endl;
	}
	
	return 0;
}

G.子串简写

题目内容

程序猿圈子里正在流行一种很新的简写方法:
对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。
例如 \(internationalization\) 简写成 \(i18n,Kubernetes\) 简写成 \(K8s, Lanqiao\) 简写成 \(L5o\) 等。
在本题中,我们规定长度大于等于 \(K\) 的字符串都可以采用这种简写方法。
长度小于 \(K\) 的字符串不允许使用这种简写。
给定一个字符串 \(S\) 和两个字符 \(c_1\)\(c_2\)
请你计算 \(S\) 有多少个以 \(c_1\) 开头 \(c_2\) 结尾的子串可以采用这种简写?

输入格式

第一行包含一个整数 \(K\)
第二行包含一个字符串 \(S\) 和两个字符 \(c_1\)\(c_2\)
对于 \(20\%\) 的数据,\(2 ≤ K ≤ |S| ≤ 10000\)
对于 \(100\%\) 的数据,\(2 ≤ K ≤ |S| ≤ 5 × 10^5\)
\(S\) 只包含小写字母。\(c_1\)\(c_2\) 都是小写字母。
\(|S|\) 代表字符串 \(S\) 的长度。

输出格式

一个整数代表答案

输入样例

4
abababdb a b

输出样例

6

符合条件的子串如下所示,中括号内是该子串:
\([abab]abdb\)
\([ababab]db\)
\([abababdb]\)
\(ab[abab]db\)
\(ab[ababdb]\)
\(abab[abdb]\)

思路

先求出 \(c_1\) 的前缀和数组 \(s[i]\) ,统计\(c_1\)在前缀中出现的次数。接着遍历字符串,每遇到一次 \(c_2\) ,就加上 \(s[i-k+1]\) ,即加上以 \(c_1\) 开头 \(c_2\) 结尾且长度大于等于 \(K\) 的字符串,最后得到答案。
\(Tips:\)注意最后答案可能很大,要开 \(longlong\)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
char str[N];
ll cnt[N];
char st,ed;
int k;

int main()
{
	cin>>k;
	
	cin>>str+1>>st>>ed;
	
	int n=strlen(str+1);
	//统计c1的前缀和
	for(int i=1;i<=n;i++)
		if(str[i]==st)cnt[i]=cnt[i-1]+1;
		else cnt[i]=cnt[i-1];
	
	
	//累加求个数
	ll res=0;
	for(int i=k;i<=n;i++)
		if(str[i]==ed)res+=cnt[i-k+1];
	
	
	cout<<res<<endl;
	
	return 0;
}

H.整数删除

题目内容

给定一个长度为 \(N\) 的整数数列:\(A_1, A_2, . . . , A_N\) 。你要重复以下操作 \(K\) 次:
每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值。输出 \(K\) 次操作后的序列。

输入格式

第一行包含两个整数 \(N\)\(K\)
第二行包含 \(N\) 个整数,\(A_1, A_2, A_3, . . . , A_N\)

输出格式

输出 \(N − K\) 个整数,中间用一个空格隔开,代表 \(K\) 次操作后的序列。

输入样例

5 3
1 4 2 8 7

输出样例

17 7

数列变化如下,中括号里的数是当次操作中被选择的数:

[1] 4 2 8 7
5 [2] 8 7
[7] 10 7
17 7

对于 \(20\%\) 的数据,\(1 ≤ K < N ≤ 10000\)
对于 \(100\%\) 的数据,\(1 ≤ K < N ≤ 5 × 10^5,0 ≤ A_i ≤ 10^8\)

思路

题目关键是删除数列最小值,并且动态维护最小值。若取出最小元素的值比原来有变化,要重新放入优先队列中判断;否则就将其删除

  • 删除操作考虑使用双链表,进行 \(O(1)\) 删除
  • 最小值利用优先队列去维护

代码

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N=5e5+10;
typedef long long ll;
typedef pair<ll,int>PII;
ll e[N],l[N],r[N];

priority_queue<PII,vector<PII>,greater<PII>>q;

int n,k;

//双链表删除
void dele(int k)
{
	l[r[k]]=l[k];
	r[l[k]]=r[k];
	e[l[k]]+=e[k];
	e[r[k]]+=e[k];
	
}

int main()
{
	cin>>n>>k;
	
	//双链表的头尾
	r[0]=1,l[n+1]=n;
	
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		e[i]=x,l[i]=i-1,r[i]=i+1;
		q.push({e[i],i});//优先队列,小根堆,储存值和编号
	}
	
	while(k--)
	{
		auto t=q.top();
		q.pop();
		
		//取出最小元素,如果值有变化,重新放入优先队列中;否则将其删除
		if(t.x!=e[t.y])q.push({e[t.y],t.y}),k++;
		else dele(t.y);	
	}
	
	for(int i=r[0];i!=n+1;i=r[i])
		cout<<e[i]<<' ';
	
	
	return 0;
}

I.景区导游

题目内容

某景区一共有 \(N\) 个景点,编号 \(1\)\(N\)。景点之间共有 \(N − 1\) 条双向的摆渡车线路相连,形成一棵树状结构。在景点之间往返只能通过这些摆渡车进行,需要花费一定的时间。
小明是这个景区的资深导游,他每天都要按固定顺序带客人游览其中 \(K\) 个景点:\(A_1, A_2, . . . , A_K\) 。今天由于时间原因,小明决定跳过其中一个景点,只带游客按顺序游览其中 \(K − 1\) 个景点。具体来说,如果小明选择跳过 \(A_i\),那么他会按顺序带游客游览 $ A_1, A_2, . . . , A_{i−1}, A_{i+1}, . . . , A_K, (1 ≤ i ≤ K) $。
请你对任意一个 \(A_i\),计算如果跳过这个景点,小明需要花费多少时间在景点之间的摆渡车上?

输入格式

第一行包含 \(2\) 个整数 \(N\)\(K\)
以下 \(N − 1\) 行,每行包含 \(3\) 个整数 \(u, v\)\(t\),代表景点 \(u\)\(v\) 之间有摆渡车线路,花费 \(t\) 个单位时间。
最后一行包含 \(K\) 个整数 \(A_1, A_2, . . . , A_K\) 代表原定游览线路。

输出格式

输出 \(K\) 个整数,其中第 \(i\) 个代表跳过 \(A_i\) 之后,花费在摆渡车上的时间。

输入样例

6 4
1 2 1
1 3 1
3 4 2
3 5 2
4 6 3
2 6 5 1

输出样例

10 7 13 14

原路线是 \(2 → 6 → 5 → 1\)
当跳过 \(2\) 时,路线是 \(6 → 5 → 1\),其中 \(6 → 5\) 花费时间 \(3 + 2 + 2 = 7,5 → 1\) 花费时间 \(2 + 1 = 3\),总时间花费 \(10\)
当跳过 \(6\) 时,路线是 \(2 → 5 → 1\),其中 \(2 → 5\) 花费时间 \(1 + 1 + 2 = 4,5 → 1\) 花费时间 \(2 + 1 = 3\),总时间花费 \(7\)
当跳过 \(5\) 时,路线是 \(2 → 6 → 1\),其中 \(2 → 6\) 花费时间 \(1 + 1 + 2 + 3 = 7,6 → 1\) 花费时间 \(3 + 2 + 1 = 6\),总时间花费 \(13\)
当跳过 $1 $时,路线时 \(2 → 6 → 5\),其中 \(2 → 6\) 花费时间 \(1 + 1 + 2 + 3 = 7,6 → 5\) 花费时间 \(3 + 2 + 2 = 7\),总时间花费 \(14\)

对于 \(20\%\) 的数据,\(2 ≤ K ≤ N ≤ 10^2\)
对于 \(40\%\) 的数据,\(2 ≤ K ≤ N ≤ 10^4\)
对于 \(100\%\) 的数据,\(2 ≤ K ≤ N ≤ 10^5,1 ≤ u, v, A_i ≤ N,1 ≤ t ≤ 10^5\)。保证 \(A_i\) 两两不同。

思路

\(LCA\)板子题,题目中是一棵树形图,求用时即为求树上任意两点间的距离,可利用\(LCA\)快速求出两点之间的距离。求树上两个点距离的时候,可以预处理出每个点到根节点的距离。
两点间最短距离公式: \(x\)\(y\) 的距离 \(= d[x]+d[y] - 2*d[lca(x,y)]\) ,本题可以先计算不跳过景点时的总用时,之后分类讨论

  • 删除第 \(1\) 个结点时,减去 \(1 \sim 2\) 之间的用时即可
  • 删除第 \(k\) 个结点时,减去 \(k-1 \sim k\) 之间的用时
  • 其他情况减去 \(i-1 \sim i,i\sim i+1\) 之间的用时,并且加上 \(i-1 \sim i+1\) 之间的用时
    时间复杂度:预处理 \(O(nlogn)\) 查询:\(O(logn)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=N*2;
typedef long long ll;
int h[N],e[M],ne[M],w[M],idx;
int f[N][20];
int depth[N];
ll d[N];
int A[N];

void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

//计算所有结点到根节点1的距离
void bfs()
{
    queue<int>q;
    depth[1]=1;
    q.push(1);
    
    while(q.size()){
        int t=q.front();
        q.pop();
        
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(depth[j])continue;
            q.push(j);
            
            if(depth[j])continue;
            depth[j]=depth[t]+1;
            d[j]=d[t]+w[i];
            f[j][0]=t;
            for(int k=1;k<=19;k++)
                f[j][k]=f[f[j][k-1]][k-1];
            
        }
    }
    
}


//倍增法求lca
int lca(int a,int b)
{
    if(depth[a]<depth[b])swap(a,b);
    
    for(int i=19;i>=0;i--)
        if(depth[f[a][i]]>=depth[b])
            a=f[a][i];
    
    
    if(a==b)return a;
    for(int i=19;i>=0;i--)
        if(f[a][i]!=f[b][i])
            a=f[a][i],b=f[b][i];
    
    
    return f[a][0];
}

int main()
{ 	
	
	memset(h,-1,sizeof h);
	int n,k;
   
    cin>>n>>k;
    
    for(int i=0;i<n-1;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    
    bfs();
    
    
    for(int i=1;i<=k;i++)
    	cin>>A[i];
    

    //求不删除结点前的总用时
    ll res=0;
    for(int i=2;i<=k;i++)
    {
    	int p=lca(A[i],A[i-1]);
		res+=d[A[i]]+d[A[i-1]]-2*d[p];
	}
	
	
    for(int i=1;i<=k;i++)
    {
    	ll dist;

		if(i==1)//删除第一个结点时,减去1~2之间的用时即可
		{
    		int p=lca(A[1],A[2]);
			dist=d[A[1]]+d[A[2]]-2*d[p];
			cout<<res-dist<<' ';
				
		}
		else if(i==k)//删除第k个结点时,减去k-1~k之间的用时
		{
			int p=lca(A[k],A[k-1]);
			dist=d[A[k]]+d[A[k-1]]-2*d[p];
			cout<<res-dist<<' ';
			
		}
		else{//其他情况减去i-1~i,i~i+1之间的用时,并且加上i-1~i+1之间的用时
			
			int p1=lca(A[i-1],A[i]);
			int p2=lca(A[i],A[i+1]);
			int p3=lca(A[i-1],A[i+1]);
			dist=d[A[i-1]]+d[A[i]]-2*d[p1]+d[A[i]]+d[A[i+1]]-2*d[p2];
			
			cout<<res-dist+d[A[i-1]]+d[A[i+1]]-2*d[p3]<<' ';
			
		}
		
	}
    
    
    return 0;
    
}

J.砍树

题目内容

题目描述
给定一棵由 \(n\) 个结点组成的树以及 \(m\) 个不重复的无序数对 $(a_1, b_1), (a_2, b_2), ... , (a_m, b_m) $,其中 \(a_i\) 互不相同,\(b_i\) 互不相同,\(a_i ≠ b_j(1 ≤ i, j ≤ m)\)
小明想知道是否能够选择一条树上的边砍断,使得对于每个 \((a_i, b_i)\) 满足 \(a_i\)\(b_i\) 不连通。
如果可以则输出应该断掉的边的编号(编号按输入顺序从 \(1\) 开始),否则输出 \(-1\)

输入格式

输入共 \(n + m\) 行,第一行为两个正整数 \(n,m\)
后面 \(n − 1\) 行,每行两个正整数 \(x_i,y_i\) 表示第 \(i\) 条边的两个端点。
后面 \(m\) 行,每行两个正整数 \(a_i,b_i\)
对于 \(30\%\) 的数据,保证 \(1 < n ≤ 1000\)
对于 \(100\%\) 的数据,保证 \(1 < n ≤ 100000,1 ≤ m ≤ n / 2\)

输出格式

一行一个整数,表示答案,如有多个答案,输出编号最大的一个。

输入样例

6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

输出样例

4

断开第 2 条边后形成两个连通块:{3, 4},{1, 2, 5, 6},满足 3 和 6 不连通,4 和 5 不连通。
断开第 4 条边后形成两个连通块:{1, 2, 3, 4},{5, 6},同样满足 3 和 6 不连通,4 和 5 不连通。
4 编号更大,因此答案为 4。

思路

$LCA + $树上差分模板题,若砍掉某条边让这两点不连通,那么这条边一定是从 \(x\)\(y\) 路径上的一点,我们可以利用树上差分,让 \(diff[x]+1,diff[y]+1,diff[lca(x,y)]-2\) ,最后做一遍 \(dfs\) 求和,让从 \(x\)\(y\) 路径的边权值都加1,只需要从编号最大的倒序遍历,若存在一条边的值为 \(m\),则该边即为所求答案。若不存在,则输出 \(-1\)

代码

#include<bits/stdc++.h>
#define x first
#define y second;
using namespace std;
const int N=1e5+10,M=N*2;
typedef pair<int,int>PII;
int h[N],e[M],ne[M],idx;
int depth[N];//记录深度
int f[N][20];
int diff[N];//差分数组
PII edge[N];//记录每条边的编号
int n,m;	

void add(int a,int b)
{
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}

void dfs(int u,int fa)
{
	depth[u]=depth[fa]+1;
	
	f[u][0]=fa;
	
	for(int i=1;i<=19;i++)
		f[u][i]=f[f[u][i-1]][i-1];
		
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j!=fa)
		{
			dfs(j,u);
		}
	}
	
}

//倍增法求lca
int lca(int a,int b)
{
	if(depth[a]<depth[b])swap(a,b);
	
	for(int i=19;i>=0;i--)
		if(depth[f[a][i]]>=depth[b])
			a=f[a][i];
			
			
	
	if(a==b)return a;
	
	for(int i=19;i>=0;i--)
		if(f[a][i]!=f[b][i])
			a=f[a][i],b=f[b][i];
			
	return f[a][0];
}

//利用dfs对树上的差分数组求和
void dfs1(int u,int fa)
{
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];	
		if(j!=fa)
		{
			dfs1(j,u);
			diff[u]+=diff[j];
		}
	}
}

int main()
{
	memset(h,-1,sizeof h);
	cin>>n>>m;
	
	for(int i=1;i<n;i++)
	{
		int a,b;
		cin>>a>>b;
		edge[i]={a,b};
		add(a,b),add(b,a);
		
	}
	
	dfs(1,0);
	
	for(int i=0;i<m;i++)
	{
		int a,b;
		cin>>a>>b;
		int p=lca(a,b);
		
		diff[a]++,diff[b]++;
		diff[p]-=2;
	}
	  
	dfs1(1,0);
	
	
	int res=-1;
	for(int i=n-1;i>=1;i--)
	{
		int a=edge[i].x,b=edge[i].y;
		if(depth[a]<depth[b])swap(a,b);//边的权值保存在深度大的节点上
		if(diff[a]==m)
		{
			res=i;
			break;
		}
		
	}
	cout<<res<<endl;
	return 0;
}

原文链接:https://www.cnblogs.com/zzmxj/p/17346697.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:第14届蓝桥杯C++B组省赛题解(A-J)(更新完毕) - Python技术站

(0)
上一篇 2023年4月24日
下一篇 2023年4月25日

相关文章

  • Java数据结构之常见排序算法(上)

    Java数据结构之常见排序算法(上) 本篇文章将介绍常见的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序。这些排序算法既是学习算法和数据结构的入门知识,也是在实际工作中常用的基础算法。 冒泡排序 冒泡排序是一种简单的排序算法,它的基本思想是从前往后依次比较相邻的两个元素,如果前面的元素比后面的元素大,则交换它们的位置,重复这个过程,每一轮比较…

    数据结构 2023年5月17日
    00
  • Python字符串的全排列算法实例详解

    Python字符串的全排列算法实例详解 在Python中,字符串的全排列算法是一种常见的算法,它可以用于字符串的排序、组合、查找等问题。本文将详细介绍Python字符串的全排列算法,包括递归实现和迭代实现两种方法。 1. 递归实现 递归实现是一种常用的字符串全排列算法,它的本思想是将分为两部分第一个字符和剩余字符。然后将第一个字符与剩余字符的全排列进行组合,…

    python 2023年5月14日
    00
  • 详解插值查找算法原理与使用方法

    一下是对 “插值查找算法” 的详细讲解、作用以及使用方法攻略。 什么是插值查找算法? 插值查找算法属于一种查找算法,类似于二分查找。不同的是,插值查找算法是按比例分配查找的位置,而不是固定地分配。插值算法假设数据有序是的基础上,根据所要查找数据的范围值与数组中最大、最小范围值的比例,算出所要查找元素应该处于数组的哪个位置。 插值查找算法的时间复杂度为 O(l…

    算法 2023年3月27日
    00
  • Python 数据结构之旋转链表

    Python 数据结构之旋转链表 简介 在进行链表操作时,有时需要旋转链表的一部分,即将链表的最后几个节点移到链表的头部。本文将讲解 Python 实现旋转链表的方法。 方法 我们需要了解两个概念:旋转链表、链表反转。 旋转链表 假设链表为1-2-3-4-5,k=2,将链表后两个节点移动到链表头部,即转化为4-5-1-2-3。 做法如下: 先遍历链表,得出链…

    数据结构 2023年5月17日
    00
  • C++20中的结构化绑定类型示例详解

    ” C++20中的结构化绑定类型示例详解 ” 具体攻略如下: 什么是结构化绑定类型? 结构化绑定类型是C++17中的新特性,它可以让我们将一个复杂类型的元素绑定到某个变量上,从而更方便地使用这些元素。 C++20还进一步扩展了结构化绑定类型的功能,可以通过给用于引用的名字声明类型来进行显式类型的绑定。 结构化绑定类型的基本用法 下面的例子展示了如何使用结构化…

    数据结构 2023年5月17日
    00
  • 使用C语言详解霍夫曼树数据结构

    使用C语言详解霍夫曼树数据结构 什么是霍夫曼树 霍夫曼树是一种带权路径长度最短的树,也称为最优二叉树,它是优化编码的核心算法。 在霍夫曼树中,每个叶子节点对应一个字符,该节点的权值为该字符出现的次数。当然,字符可以是任何数据类型。生成霍夫曼树后,在对每个字符进行编码时,字符在霍夫曼树中的路径即为其编码。(一般规定,一条从根到叶子的路径上只出现0或1,从根到某…

    数据结构 2023年5月17日
    00
  • C语言数据结构中约瑟夫环问题探究

    C语言数据结构中约瑟夫环问题探究 什么是约瑟夫环问题? 约瑟夫环问题(Josephus problem)是一个经典的问题,据说是Flavius Josephus发现并命名的。该问题描述为,编号从1到n的n个人按照顺时针方向围坐成一圈,每人持有一个密码。从第1个人开始,顺时针方向每次完整的数m个人,然后让这m个人出圈并把他们的密码拿走不算。当到达队尾时,又从队…

    数据结构 2023年5月17日
    00
  • 【ACM算法竞赛日常训练】DAY3题解与分析【旅游】【tokitsukaze and Soldier】

    DAY3共2题: 旅游 tokitsukaze and Soldier ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 原文链接(阅读原文获得更好阅读体验): 旅游 题目传送门:https://ac.nowcoder…

    算法与数据结构 2023年4月18日
    00
合作推广
合作推广
分享本页
返回顶部