十四届蓝桥杯C++B组题解

T1:日期统计

解题思路

思路:2023年只有365天,枚举2023的每一天是否在数组中存在即可,时间复杂度O(365*100)

注意:还有就是不要傻傻的去复制数组加逗号了,直接输入就好了(我以前就这样)

日期问题可以看看我之前写过的博客:蓝桥杯备赛之日期问题-CSDN博客

代码

// 直接枚举2023年的所有日期即可
// 时间复杂度o(365*100)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define f first
#define s second
const int N = 1e5 + 50;
int dates[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};

void solve() {
	int ans = 0;
	int a[120];
	for (int i = 1;i <= 100; i++) cin >> a[i];
	for (int month = 1;month <= 12; month++) { // 枚举月份
		for (int date = 1;date <= dates[month]; date++) { // 枚举日
		    int b[9] = {0,2,0,2,3,month/10,month%10,date/10,date%10}; 
			int cnt = 1;
			for (int i = 1;i <= 100; i++){
				if (b[cnt] == a[i]) cnt++;
				if (cnt == 9) {
					ans++;
					break;
				}
			}
		}
	}
	cout << ans; // 235
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t = 1;
	while (t--) {
		solve();
	}
	return 0;
}

T2:01串的熵

解题思路

思路: 0的个数比1少,直接枚举0的个数即可,时间复杂度最坏O(11666666),当然这是填空题,能出答案就行

注意:要求的H值的精度为小数点后四位,我们判断就判小数点后四位,过大或过小答案都会有偏差,不要认为精度越小答案越准确!!!

代码

#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e5 + 50;
const double P = 11625907.5798;
int sum = 23333333;
void solve() {
    // c0 < c1
    for (double i = sum/2; i >= 0; i--) { // 枚举0的个数
    	double p0 = i / sum; // 0的占比
    	double p1 = (sum - i) / sum; // 1的占比
    	double h = -(p0*i)*log2(p0)-(p1*(sum-i))*log2(p1);
    	// if (h - P <= 1e-6) { // 精度1e-6的答案为11027420
        if (h - P <= 1e-4) { 
    		cout << (int)i; // 11027421
    		return ;
    	}
    }
    return ;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

T3:冶炼金属

解题思路

思路:求解的是转化率的可能范围,即求区间的边界,二分其中一个边界,枚举另一个边界即可

代码

#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e4 + 50;
int n; 
vector<pair<int,int> > a(N);

bool check(int mid) {
	for (int i = 1;i <= n; i++) {
		if (a[i].f / a[i].s < mid) return false; // 可以更小
	}
	return true; 
}
void solve() {
    cin >> n;
    int maxx = 0;
    for (int i = 1;i <= n; i++) {
    	cin >> a[i].f >> a[i].s;
    	maxx = max(maxx,a[i].f/a[i].s);
    }

    int l = 0,r = maxx+1; // 二分枚举转化率的最大值
    while (l < r) {
    	int mid = (l + r + 1) >> 1;
    	if (check(mid)) l = mid;
    	else r = mid - 1;
    }
    
    int max_x = r,min_x;
    int f = 0;
    for (int i = max_x;i >= 0; i--) {  // 枚举最小转化率
    	for (int j = 1;j <= n; j++) {
    		if (a[j].f / i != a[j].s) {
    			min_x = i + 1;
    			f = 1;
    			break;
    		}
    	}
    	if (f) break;
    }
    cout << min_x << " " << max_x;
    return ;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

T4:飞机降落

解题思路

首先我们看题目的范围,飞机架数和测试样例最多10,明显可以暴力求解!!!,下面提供两种暴力解法

  1. DFS:DFS搜索每一个飞机降落的时机,当所有飞机成功降落时递归结束,注意判重与回溯(常用)
  2. 全排列函数:直接对数组的下标取全排列,获得所有可能的飞机降落顺序,找到一种合法顺序即可返回YES

时间复杂度最坏:O(10*10!)

代码

DFS

// DFS
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e5 + 50;
struct node {
	int t; // 最早降落时间
	int d; // 周旋时间
	int l; // 降落花费的时间
}a[20];
int n;
bool st[N]; // 标记当前飞机是否降落
// 已经降落了几架飞机以及上一架飞机降落的时间
bool dfs(int u,int last) {
	if (u >= n) return true; // 飞机全部安全降落,递归退出点

    for (int i = 1;i <= n; i++) {
        if (st[i]) continue;
        // 1.判断当前飞机是否可以降落
        if (a[i].t + a[i].d < last) return false; // 不能降落,回溯
        // 2.思考u+1个位置由谁降落
        st[i] = true;
        if (dfs(u + 1,max(a[i].t,last) + a[i].l)) return true;
        st[i] = false;
    }
    return false;
}
void solve() {
    cin >> n; // 飞机架数
    for (int i = 1;i <= n; i++) st[i] = false; // 初始化st数组
    for (int i = 1;i <= n; i++) {
    	cin >> a[i].t >> a[i].d >> a[i].l;
    }
    if (dfs(0,0)) cout << "YES" << '\n';
    else cout << "NO" << '\n';
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t; cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

全排列函数

// 只要找到一种可安全降落的方案即可
// nextpermutation做法
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

struct node {
	int t; // 最早降落时间
	int d; // 周旋时间
	int l; // 降落花费的时间
}a[20];
int n;
void solve() {
    cin >> n; // 飞机架数
    int id[20];
    for (int i = 1;i <= n; i++) {
    	cin >> a[i].t >> a[i].d >> a[i].l;
    	id[i] = i;
    }
    // 枚举飞机降落的所有可能情况
    do {
    	int f = 0;
    	int last = a[id[1]].t + a[id[1]].l; // 上一架飞机完成降落的时间
    	// 判断当前是否为合法降落顺序
    	for (int j = 2;j <= n; j++) { 
    		int i = id[j];
    		int now = a[i].t + a[i].d; // 当前飞机最晚降落时间
    		if (now >= last) {  // 可以降落
    			// if (a[i].t > last) last = a[i].t + a[i].l;
    			// else last = last + a[i].l;
                last = max(last,a[i].t) + a[i].l;
    		}
    		else { // 无法降落
    			f = 1;
    			break;
    		}
    	}
    	if (!f) {
    		cout << "YES" << '\n';
    		return ;
    	}
    }while(next_permutation(id + 1,id + n + 1));

    // 无法安全降落
    cout << "NO" << '\n';
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t; cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

T6:岛屿个数

解题思路

前提:下面将外面一圈海称为外海,非子岛屿称为外岛,那么我们要求的是外岛的数量;

下面的地图中,(1,1)的0就是外海

0 1 1 1 1

1 1 0 0 1

1 0 1 0 1

1 0 0 0 1

1 1 1 1 1 

我们从每一个外海开始做BFS,搜索到的岛的一定是外岛,然后标记该外岛,继续求下一个外岛;这里标记外岛既可以用DFS,也可以用BFS

注意:搜索外海是8个方向,搜索外岛是4个方向,当没有外海时整体就是一个外岛;

比如一个5*5的地图,并没有外海,所以直接输出答案1即可

1 1 1 1 1

1 0 0 0 1

1 0 1 0 1

1 0 0 0 1

1 1 1 1 1

代码:

#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
using pii = pair<int,int>;

const int N = 55;
int dx[] = {0,0,0,-1,1,-1,1,-1,1};
int dy[] = {0,1,-1,0,0,1,1,-1,-1};
int g[N][N],st_sea[N][N],st_land[N][N];
int n,m,ans;

// 判边界
bool check(int x, int y) {
	return (x >= 1 && x <= n && y >= 1 && y <= m);
}

// BFS标记外海
void bfs_land(int x,int y) {
	queue<pii> q;
	q.push({x,y});
	while (q.size()) {
		auto p = q.front(); q.pop();
		for (int i = 1;i <= 4; i++) {
			int nx = p.f + dx[i];
			int ny = p.s + dy[i];
			if (!check(nx,ny) || st_land[nx][ny] || !g[nx][ny]) continue;
			st_land[nx][ny] = 1;
			q.push({nx,ny});
		}
	}
}

// DFS标记外岛
void dfs_land(int x,int y) {
	for (int i = 1;i <= 4; i++) { // 4个方向
		int nx = x + dx[i];
		int ny = y + dy[i];
		if (!check(nx,ny) || st_land[nx][ny] || !g[nx][ny]) continue ;
		st_land[nx][ny] = 1;
		dfs_land(nx,ny);
		// 不需要回溯,标记陆地一条路走到黑就行了
	}
}

// 遍历外海
void bfs_sea(int x,int y) {
	queue<pii> q;
	q.push({x,y});
	st_sea[x][y] = 1;
	while (q.size()) {
		auto p = q.front(); q.pop();
		for (int i = 1;i <= 8; i++) { // 8个方向
			int nx = p.f + dx[i];
			int ny = p.s + dy[i];
			if (!check(nx,ny)) continue;
			// (1) 外海
			if (!st_sea[nx][ny] && !g[nx][ny]) {
				st_sea[nx][ny] = 1;
				q.push({nx,ny});
			}
			// (2) 外岛
			if (!st_land[nx][ny] && g[nx][ny]) {
				st_land[nx][ny] = 1;
				ans++;
				bfs_land(nx,ny);
			}
		}
	}
}

void solve() {
    cin >> n >> m;
    ans = 0;
    for (int i = 1;i <= n; i++) {
    	string s; cin >> s; s = " " + s;
    	for (int j = 1;j <= m; j++) {
    		g[i][j] = s[j] - '0';
    		// 顺便初始化
    		st_sea[i][j] = st_land[i][j] = 0;
    	}
    }

    int flag = 0; // 标记是否存在外海
    for (int i = 1;i <= n; i++) {
    	for (int j = 1;j <= m; j++) {
    		if (i==1||i==n||j==1||j==m) { // 是外海
    			if (!st_sea[i][j] && !g[i][j]) { // 未被访问过
    				flag = 1;
    				bfs_sea(i,j);
    			}
    		}
    	}
    }

    if (!flag) cout << "1" << '\n'; // 无外海
    else cout << ans << '\n';
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t; cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

T7:子串简写

解题思路

简单前缀和

说明蓝桥杯题目难度不是有序的,大家要每个题都看一看

代码

#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e5 + 50;
void solve() {
	int ans = 0;
	int k; cin >> k;
	string s; cin >> s;
	char c1,c2; cin >> c1 >> c2;
	int t = 0;
	for (int i = 0;i < (int)s.size(); i++) {
		if (s[i] == c1) t++;
		if (s[i + k - 1] == c2) ans += t; // 前面的c1均可与该c2组成合法答案
	}
	cout << ans;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

T8:整数删除

解题思路

涉及两个操作

  • 动态查找最小值

使用小根堆的优先队列维护最小值

priority_queue<pii, vector<pii>, greater<pii> > q; // 小根堆
  • 删除某一个值

使用双链表维护一个结点的左右结点

对于下标为i的结点,一开始是这个状态

l[i] = i - 1;
r[i] = i + 1;

删除一个结点,直接将该结点的左节点指向右节点,右节点指向左节点

r[l[i]] = r[i];
l[r[i]] = l[i];

​​​​​​​

然后要注意的是:

删除操作之后a数组的值会改变,同时也要更新优先队列中的值,更新方式是取出一个最小值时,如果当前优先队列中的值与a数组中的值不同,就将新的值加入优先队列(优先队列中的那个值也会被及时pop()出去),该次不做删除操作;

   	if (num != a[d]) { // a数组中该值已经改变,优先队列中也要更新
    		q.push({a[d],d});
    		continue;
    }

举个例子:

a[] = {1,2,3}   k = 2

q = { {1,1}, {2,2}, {3,3} }

第一次:从q中取出1,1 = a[1],不用操作,删除该值

               a[] = {-1,3,3}

               q = { {2,2},{3,3} }

第二次:从q中取出2,a[2]已经更新为3,2 != a[2],将{a[2],2}push到q中

               a[] = {-1,3,3}

               q = { {3,2} {3,3} }

第三次:从q中取出3,3 = a[2],不用操作,删除该值

             a[] = {-1,-1,6}

             q = { {3,3} }

第四次 重复第二次的操作...

有人可能会问为什么第3次删除第二个元素的时候,只对3 加了3,为什么1号元素-1不会加?

因为1号结点被删除后,2号结点的左结点已经不是1号结点了(这里为空),所以也并不会对它进行操作;

代码

#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;
using pii = pair<int,int>;

const int N = 5e5 + 50;
int a[N],l[N],r[N];
priority_queue<pii, vector<pii>, greater<pii> > q; // 小根堆
void solve() {
    int n,k; cin >> n >> k;
    for (int i = 1; i <= n; i++) {
    	cin >> a[i];
    	q.push({a[i], i}); // 因为相同要先删前面的,所以下标也要存
    	l[i] = i - 1; // 当前点左边点的下标
    	r[i] = i + 1; // 当前点右边点的下标
    }

    while (k) {
    	auto p = q.top(); q.pop();
    	int num = p.f; // 值
    	int d = p.s; // 下标
    	if (num != a[d]) { // a数组中该值已经改变,优先队列中也要更新
    		q.push({a[d],d});
    		continue;
    	}

    	// 左右加上值
    	a[l[d]] += num;
    	a[r[d]] += num;

    	// 删除该结点
    	r[l[d]] = r[d];
    	l[r[d]] = l[d];

    	a[d] = -1; // 标记已经被删除
    	k--;
    }

    for (int i = 1;i <= n; i++) {
    	if (a[i] != -1) cout << a[i] << " ";
    }
    return ;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

T9:景区导游

解题思路

LCA+树上前缀和模板题

注意一个性质:两个点之间的路径一定经过他们的LCA

树上前缀和求的是根结点到某个结点的距离,那么两个结点的距离就为:

sum[a] + sum[b] - 2*sum[LCA(a,b)]

1.如果去掉的是两边的景点,就减去当前景点和它左/右边景点的距离

2.不是两边就要减掉它和左边右边两个景点的距离,剪多的要加回来

代码

// 树上前缀和 + LCA
// s(a,b) = sum[a] + sum[b] - 2*(sum[LCA(a,b)])
// s(a,b)是a,b之间的距离
// sum记录的是根节点到该点的距离
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e5 + 50;
struct Edge {
	int to,next,w;
}edge[N*2];
int head[2*N],cnt;

void add(int u,int v,int w) {
	edge[++cnt].w = w;
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
//-----以上为链式前向星-----//

int n,k; 
int deep[N],fa[N][30];
int sum[N]; // 前缀和数组
int st[N]; // 标记数组
void dfs(int x,int father) {
	deep[x] = deep[father] + 1; // 当前结点的深度=父节点+1
	fa[x][0] = father;
	
	for (int i = 1;(1<<i) <= deep[x]; i++) {
		fa[x][i] = fa[fa[x][i-1]][i-1];
	}
	for (int i = head[x];i;i = edge[i].next) {
		if (edge[i].to != father) {
			dfs(edge[i].to,x); // 颠鸾倒凤
		}
	}
}

void dfs1(int u, int sum1) {
	st[u] = 1;
	sum[u] = sum1;
	for (int i = head[u];i;i = edge[i].next) {
		int v = edge[i].to;
		int w = edge[i].w;
		if (!st[v]) dfs1(v,sum1 + w); 
	}
}

int LCA(int a,int b) {
	if (deep[a] < deep[b]) swap(a,b); // 始终使a的深度更深
	// 1.将a跳到和b一样的深度
	for (int i = 19; i >= 0; i--) {
		if (deep[a] - (1<<i) >= deep[b]) {
			a = fa[a][i];
		} 
		if (a == b) return a;
	}

	// 2.a,b一起向上跳
	for (int i = 19; i >= 0; i--) {
		if (fa[a][i] != fa[b][i]) {
			a = fa[a][i];
			b = fa[b][i];
		}
	}
	return fa[a][0];
}

void solve() {
    cin >> n >> k;
    int a[k + 1]; // 原路线
    for (int i = 1;i <= n - 1; i++) {
    	int u,v,w; cin >> u >> v >> w;
    	add(u,v,w); add(v,u,w);
    }
    dfs(1,0); // dfs获得结点深度
    dfs1(1,0); // 获得树上前缀和
    for (int i = 1;i <= k; i++) cin >> a[i];

    // 计算源路径总和
    int all = 0;
    for (int i = 1;i <= k - 1; i++) {
    	all += sum[a[i]] + sum[a[i + 1]] - 2*sum[LCA(a[i],a[i + 1])];
    }
    // 计算去掉第i个景点后的答案
    for (int i = 1;i <= k; i++) {
    	int ans = all;
    	if (i == 1) { // 减去1~2之间的距离
    		ans -= sum[a[1]] + sum[a[2]] - 2*sum[LCA(a[1],a[2])];
    	}
    	else if (i == k) { // 减掉k-1~k之间的距离
    		ans -= sum[a[k - 1]] + sum[a[k]] - 2*sum[LCA(a[k-1],a[k])];
    	}
    	else { // 减掉两边,加上中间减多的
    		ans -= sum[a[i - 1]] + sum[a[i]] - 2*sum[LCA(a[i-1],a[i])];
    		ans -= sum[a[i + 1]] + sum[a[i]] - 2*sum[LCA(a[i+1],a[i])];
    		ans += sum[a[i - 1]] + sum[a[i + 1]] - 2*sum[LCA(a[i-1],a[i+1])];
    	}
    	cout << ans << " ";
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

T10:砍树

解题思路

LCA+树上差分模板题

如果将m组点连成m条路经,那么只有删除m条路径中的公共边才能使m组点不连通,那我们用树上差分求每条被路径经过的次数即可,次数为m且编号最大的为答案

代码

// 将m对点连成一条一条的路径,那么答案就是每一个路径都经过的边中编号最大的边
// 两点的路径一定经过LCA,那么求出每一条被路径经过的次数即可
// 次数为m的就是可行答案,取编号最大的可行答案
#include <bits/stdc++.h>
#define int long long
#define f first
#define s second
#define all(x) x.begin(),x.end()
using namespace std;

const int N = 1e5 + 50;
struct Edge {
	int to,next;
}edge[N*2];
int head[2*N],cnt;

void add(int u,int v) {
	edge[++cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
}

//-----以上为链式前向星-----//
int n,m; 
int deep[N],fa[N][30];
int s[N]; // 差分数组
map<pair<int,int>,int> mp;
// 1.计算深度
void dfs(int x,int father) {
	deep[x] = deep[father] + 1;
	fa[x][0] = father;
	for (int i = 1;(1<<i) <= deep[i]; i++) {
		fa[x][i] = fa[fa[x][i-1]][i-1];
	}

	for (int i = head[x];i;i = edge[i].next) {
		if (edge[i].to != father) dfs(edge[i].to, x);
	}
}
// 2.计算lca
int lca(int a,int b) {
	if (deep[a] < deep[b]) swap(a,b);
	// 1.跳到同一高度
	for (int i = 19;i >= 0; i--) {
		if (deep[a] - (1<<i) <= deep[b])
			a = fa[a][i];
		if (a == b) return a;
	}
	// 2.一起向上跳
	for (int i = 19;i >= 0; i--) {
		if (fa[a][i] != fa[b][i]) {
			a = fa[a][i],b = fa[b][i];
		}
	}
	return fa[a][0];
}

// 3.计算前缀和
int res[N];
int cal_sum(int u,int fa) {
	for (int i = head[u];i;i = edge[i].next) {
		int it = edge[i].to;
		if (it != fa) {
			s[u] += cal_sum(it,u);
		}
	}
	res[mp[{fa,u}]] = s[u];
	return s[u]; 
}
//4.实现函数
void solve() {
    cin >> n >> m;
    for (int i = 1;i <= n - 1; i++) {
    	int u,v; cin >> u >> v;
    	add(u,v),add(v,u);
    	mp[{u,v}] = mp[{v,u}] = 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;
    }
    cal_sum(1,1);
    int ans = -1;
    for (int i = 1;i <= n; i++) {
    	if (res[i] == m) {
    		ans = max(ans,i);
    	}
    }
    cout << ans;
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    while (t--) {
        solve();
    }
    return 0;
}

最后还有一件事,如果你是C++/C语言,一定要写return 0;这很重要!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值