如何评价2022年第十三届蓝桥杯吧?

152 篇文章 20 订阅
1 篇文章 1 订阅

A题 裁纸刀

题面

 

做法

先横着剪,再竖着剪,发现可以在 n * m + 3 次操作后完成裁剪,直觉上感觉就是最少的,至于是不是最少的不知道怎么证明。

于是赛场上直接交了个 443。

B题 灭鼠先锋

题面

做法

推导了一会儿得出 LLLV 的结果,不放心又打了个表验证,发现果然是 LLLV。

代码

#include<bits/stdc++.h>

using namespace std;

int a[2][4];
bool dfs() {
	bool all1 = true;
	for(int i = 0; i < 2; i++) {
		for(int j = 0; j < 4; j++) {
			if(!a[i][j]) all1 = false;
		}
	}
	if(all1) return true;
	bool res = false;
	for(int i = 0; i < 2; i++) {
		for(int j = 0; j < 4; j++) {
			if(!a[i][j]) {
				a[i][j] = 1;
				if(!dfs()) res = true;
				if(j < 4 && !a[i][j + 1]) {
					a[i][j + 1] = 1;
					if(!dfs()) res = true;
					a[i][j + 1] = 0;	
				}
				a[i][j] = 0;
			}
		}
	}
	return res;
}

int main() {
	a[0][0] = 1;
	cout << dfs();
	a[0][0] = 0;
	
	a[0][1] = 1;
	cout << dfs();
	a[0][1] = 0;
	
	a[0][0] = a[0][1] = 1;
	cout << dfs();
	a[0][0] = a[0][1] = 0;
	
	a[0][1] = a[0][2] = 1;
	cout << dfs();
	a[0][1] = a[0][2] = 0;
}

C题 求和

题面

做法

题目要我们求这个式子  \sum_{i=1}^{n}\sum_{j=i+1}^{n}\large a_{i}  \large a_{j} ,可以写成\large \sum_{i=1}^{n}\large a_{i} \large \sum_{j=i+1}^{n}\large a_{j} 的形式,对于后半部分,做一个前缀和就可以在 O(1) 的时限内完成计算,因此对于整个式子就可以在 O(n) 的时限内完成计算。

具体看代码。

代码

#include<bits/stdc++.h>
#define ll long long

using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	ll n, res = 0, tmp = 0;
	cin >> n;
	vector<ll> a(n);
	for(int i = 0; i < n; i++) {
		cin >> a[i];
		res += a[i] * tmp;
		tmp += a[i];
	}
	cout << res;
	
	return 0;
}

D题 选数异或

题面 

做法

维护 f[i] 表示第 i 个位置左边第一个 A[j] 的值为 A[i] ^ x 的下标 j,这样有一个性质是 a[i] ^ a[f[i]] = x。

比如样例中,i = 3 处, 3 ^ 1 = 2,而 2 这个数字在 3 位置左边第一次出现是在 2 位置。因此 f[3] = 2。

这样,对于每个询问 [l, r],若满足  ,则该区间内存在两个数的异或为 x。

 如何求解 f[i]?从左到右扫一遍,用 pos[x] 记录数字 x 当前最右出现的位置。则对于 A[i],f[i] = pos[A[i] ^ x],同时更新 pos[A[i]] = i。对于 A[i] ^ x 不存在的情况,令 f[i] = 0 即可。

 还有需要解决的问题是快速求解 的值,我们需要一个支持区间 max 的数据结构,可以使用线段树,ST表......等各种方式维护。

 代码中我使用了线段树,预处理时间复杂度 O(nlogn),单次询问 O(logn),总的时间复杂度为 O((n + q) logn)。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = 100000, M = 20;

int st[(N + 1) << 2], pos[1 << M], Left[N + 1];
void pull(int i) {
	st[i] = max(st[i << 1], st[i << 1|1]);
}
void build(int i, int tl, int tr) {
	if(tl == tr) {
		st[i] = Left[tl];
		return;
	}
	int mid = (tl + tr) >> 1;
	build(i << 1, tl, mid);
	build(i << 1 | 1, mid + 1, tr);
	pull(i);
}
int qry(int i, int tl, int tr, int l, int r) {
	if(tl >= l && tr <= r) {
		return st[i];
	}
	int mid = (tl + tr) >> 1;
	if(mid >= r) {
		return qry(i << 1, tl, mid, l, r);
	} else if(mid + 1 <= l) {
		return qry(i << 1 | 1, mid + 1, tr, l, r);
	} else {
		return max(qry(i << 1, tl, mid, l, r), qry(i << 1 | 1, mid + 1, tr, l, r));
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n, q, x;
	cin >> n >> q >> x;
	for(int i = 1; i <= n; i++) {
		int v;
		cin >> v;
		Left[i] = pos[v ^ x];
		pos[v] = i;
	}
	build(1, 1, n);
	for(int i = 1; i <= q; i++) {
		int l, r;
		cin >> l >> r;
		if(qry(1, 1, n, l, r) >= l) cout << "yes\n";
		else cout << "no\n";
	}
	
	return 0;
}

E题 爬树的甲壳虫

题面

做法

这题我的做法非常笨,已经知道了有更简洁的做法。

令 f[i] 表示从 i 出发到达 n 的期望次数,则容易得到。

维护 a[i - 1],b[i - 1],含义是

这样,把第二个式子中的 f[0] 代入第一个式子。可以解得 f[i - 1] 关于 f[i] 的表达式

 

 可以写成以下形式:

进而有

 由于 ,因此 就是最终的答案。

 代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll P = 998244353;

ll qpow(ll a, ll n) {
	ll res = 1;
	while(n) {
		if(n & 1) res = res * a % P;
		a = a * a % P;
		n >>= 1;
	}
	return res;
}

ll inv(ll x) {
	return qpow(x, P - 2);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n;
	cin >> n;
	vector<ll> p(n + 1);
	for(int i = 1; i <= n; i++) {
		ll x, y;
		cin >> x >> y;
		p[i] = x * inv(y) % P;	
	}
	
	vector<ll> a(n + 1), b(n + 1);
	a[0] = 1, b[0] = 0;
	for(int i = 1; i <= n; i++) {
		ll c = (1 - p[i]) * inv((1 - p[i] * a[i - 1]) % P) % P;
		ll d = (1 + p[i] * b[i - 1]) % P * inv((1 - p[i] * a[i - 1]) % P) % P;
		a[i] = (a[i - 1] * c % P + P) % P;
		b[i] = ((a[i - 1] * d + b[i - 1]) % P + P) % P;
	}
	cout << b[n] << '\n';
	
	return 0;
}

F题 青蛙过河

题面

做法

先思考题意,考虑过河的一条路径,发现如果一条路径能过河,那么同样也可以从这条路径从河对岸过来。因此转化题意为:找到 2x 条过河路径。

什么是过河路径呢?相当于是 {p1, p2, p3, p4...pk} 这样一个位置序列,任意相邻的 pi 距离小于等小青蛙的跳跃能力。每次选择这样的一条路径,相应位置上的 h[i] 减去1。对于一个跳跃能力 k,如果能选出 2x 条路径,那么就说明有解。

使用二分答案的方法。很显然这里 k 越大越有能力“能完成题目所要求的事情”,因此二分是正确的,对小青蛙的跳跃能力 k 二分。

对于一个小青蛙的跳跃能力 k,如何判定呢?这个东西可以看成一个网络流模型(但我们并不采用【流】的算法去求解它),0 位置是源点,n 位置是汇点,询问最大流量。

由于它是一个 DAG(有向无环图)我们可以使用贪心算法去模拟这样一个流的过程(想一想在一个 DAG 上跑最大流会是什么样的?)

我也说不清我是怎么实现的,直接看代码吧。(也许我的实现是错的吧)

代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll INF = (ll)2e9;

int n;
ll x;
vector<ll> H;
bool check(int jump) {
	ll cnt = 0;
	vector<ll> h(n + 1);
	for(int i = 0; i < n; i++) h[i] = H[i];
	h[n] = h[0];
	queue<pair<ll, int>> Q;
	Q.push(make_pair(h[0], 0));
	for(int i = 1; i <= n; i++) {
		while(!Q.empty() && i - Q.front().second > jump) Q.pop();
		int now = 0;
		while(!Q.empty() && now < h[i]) {
			int v = min(Q.front().first, h[i] - now);
			now += v;
			Q.front().first -= v;
			if(Q.front().first == 0) Q.pop();
		}
		Q.push(make_pair(now, i));
		if(i == n) cnt = now;
	}
	return cnt >= 2 * x;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	cin >> n >> x;
	H.resize(n);
	H[0] = INF;
	for(int i = 1; i < n; i++) {
		cin >> H[i];
	}

	int l = 1, r = n;
	while(l < r) {
		int mid = (l + r) / 2;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << '\n';

	return 0;
}

G题 最长不下降子序列

题面

做法

考虑如果我们把一段区间修改成一个相同的数字,会修改成什么样的数字?

对于每个 i,我们把它右边 [i + 1, i + k] 范围内全部修改成 a[i],这样一定比把这段区间 [i + 1, i + k]修改成其他数字更优(严格来说应该是不劣)。

那么修改后的最长非降子序列长度 = 以 i 为结尾的最长非降子序列长度 + 在 i + k 位置,暂时把 a[i + k] 改成 a[i] 后,以 i + k 位置为起点的最长非降子序列长度 + (k - 1)。我们把每个位置 i 的结果算出来,取 max 即可。

我们称前者为 f[i], 后者为 g[i][j](g[i + k][a[i]])。二者都可以用线段树维护,具体看代码(我说不清楚我是怎么做的)。

代码

#include<bits/stdc++.h>

using namespace std;

const int N = (int)1e5;

int st[(N + 5) << 2];
void init(int i, int tl, int tr) {
	st[i] = 0;
	if(tl == tr) return;
	int mid = (tl + tr) >> 1;
	init(i << 1, tl, mid);
	init(i << 1 | 1, mid + 1, tr);
}
void pull(int i) {
	st[i] = max(st[i << 1], st[i << 1 | 1]);
}
void upd(int i, int tl, int tr, int p, int v) {
	if(tl == tr) {
		st[i] = max(st[i], v);
		return;
	}
	int mid = (tl + tr) >> 1;
	if(mid >= p) upd(i << 1, tl, mid, p, v);
	else upd(i << 1 | 1, mid + 1, tr, p, v);
	pull(i);
}
int qry(int i, int tl, int tr, int l, int r) {
	if(tl >= l && tr <= r) {
		return st[i];
	}
	int mid = (tl + tr) >> 1;
	if(mid >= r) {
		return qry(i << 1, tl, mid, l, r);
	} else if(mid + 1 <= l) {
		return qry(i << 1 | 1, mid + 1, tr, l, r);
	} else {
		return max(qry(i << 1, tl, mid, l, r), qry(i << 1 | 1, mid + 1, tr, l, r));
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int n, k;
	cin >> n >> k;
	vector<int> a(n), b(n), f(n), invf(n);
	map<pair<int, int>, int> g;
	vector<vector<int>> gv(n);
	for(int i = 0; i < n; i++) {
		cin >> a[i];
		b[i] = a[i];
	}
	sort(b.begin(), b.end());
	b.erase(unique(b.begin(), b.end()), b.end());
	for(int i = 0; i < n; i++) {
		a[i] = lower_bound(b.begin(), b.end(), a[i]) - b.begin();
	}
	
	init(1, 0, n);
	for(int i = 0; i < n; i++) {
		f[i] = qry(1, 0, n, 0, a[i]) + 1;
		upd(1, 0, n, a[i], f[i]);
		int r = min(n - 1, i + k);
		gv[r].push_back(a[i]);
	}
	init(1, 0, n);
	for(int i = n - 1; i >= 0; i--) {
		for(auto v : gv[i]) {
			g[make_pair(i, v)] = qry(1, 0, n, v, n) + 1;
		}
		invf[i] = qry(1, 0, n, a[i], n) + 1;
		upd(1, 0, n, a[i], invf[i]);
	}
	int ans = 0;
	for(int i = 0; i < n; i++) {
		int r = min(n - 1, i + k);
		ans = max(ans, f[i] + g[make_pair(r, a[i])] + r - i - 1);
	}
	cout << ans << '\n';
	
	return 0;
}

H题 扫描游戏

题面

做法

对每个物品极角排序,棒的长度足够碰到的物品丢进 set ,然后使用 lower_bound 找到离棒最近的一个元素。

这题就是需要让我们模拟一下这个过程,但是我赛场上看错题了,看成了棒逆时针旋转,得知是顺时针旋转后,非常痛苦。

因为不太会计算几何,我的代码写得非常混乱非常丑陋,感觉把方向改一改是对的,不过我也不确定有没有其他 bug。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

inline int to(ll x, ll y) {
	if(y > 0 || (x > 0 && y == 0)) return 0;
	else return 1;
}
struct Point {
	ll x, y, z, id;
	bool operator<(const Point& P2) const {
		if(x * P2.y - P2.x * y == 0) return id < P2.id;
		else return x * P2.y - P2.x * y > 0;
	}
	bool operator==(const Point& P2) const {
		return x * P2.y - P2.x * y == 0 && id == P2.id;
	}
};
bool cmp(Point A, Point B) {
	return A.x * A.x + A.y * A.y > B.x * B.x + B.y * B.y;
}
int cross(Point A, Point B) {
	return A.x * B.y - B.x * A.y;	
}
bool sameRank(Point A, Point B) {
	return to(A.x, A.y) == to(B.x, B.y) && cross(A, B) == 0;	
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int n, rank = 0, add = 0;
	ll L;
	cin >> n >> L;
	ll nowx = 0, nowy = L;
	vector<int> res(n, -1);
	vector<Point> p[2], solved;
	set<Point> s[2];
	for(int i = 0; i < n; i++) {
		Point pnt;
		cin >> pnt.x >> pnt.y >> pnt.z;
		pnt.id = i;
		if(pnt.x == 0 && pnt.y == 0) {
			if(rank == 0) rank = 1, add = 0;
			else add += 1;
			res[i] = rank;
			L += pnt.z;
			// !!!
			pnt.y = L;
			solved.push_back(pnt);
		} else {
			p[to(pnt.x, pnt.y)].push_back(pnt);
		}
	}
	rank += add;
	add = 0;
	sort(p[0].begin(), p[0].end(), cmp);
	sort(p[1].begin(), p[1].end(), cmp);
	
	bool tag;
	do {
		tag = false;
		while(!p[0].empty()) {
			if(L * L >= p[0].back().x * p[0].back().x + p[0].back().y * p[0].back().y) {
				s[0].insert(p[0].back());
				p[0].pop_back();
			} else {
				break;
			}
		}
		while(!p[1].empty()) {
			if(L * L >= p[1].back().x * p[1].back().x + p[1].back().y * p[1].back().y) {
				s[1].insert(p[1].back());
				p[1].pop_back();
			} else {
				break;
			}
		}
		int nowP = to(nowx, nowy);
		Point tmp;
		tmp.x = nowx, tmp.y = nowy, tmp.z = -1, tmp.id = -1;
		auto nxt = s[nowP].lower_bound(tmp);
		if(nxt != s[nowP].end()) {
			if(solved.empty() || !sameRank(*nxt, solved.back())) {
				rank += add + 1;
				add = 0;
			} else {
				add += 1;
			}
			solved.push_back(*nxt);
			res[nxt->id] = rank;
			L += nxt->z;
			nowx = nxt->x, nowy = nxt->y;
			s[nowP].erase(nxt);
			tag = true;
		} else {
			if(nowP == 0) {
				tmp.x = -1, tmp.y = 0;
			} else {
				tmp.x = 1, tmp.y = 0;
			}
			nowP ^= 1;
			auto qwq = s[nowP].lower_bound(tmp);
			if(qwq != s[nowP].end()) {
				if(solved.empty() || !sameRank(*qwq, solved.back())) {
					rank += add + 1;
					add = 0;
				} else {
					add += 1;
				}
				solved.push_back(*qwq);
				res[qwq->id] = rank;
				L += qwq->z;
				nowx = qwq->x, nowy = qwq->y;
				s[nowP].erase(qwq);
				tag = true;	
			} else {
				if(nowP == 0) {
					tmp.x = -1, tmp.y = 0;
				} else {
					tmp.x = 1, tmp.y = 0;
				}
				nowP ^= 1;
				auto tvt = s[nowP].lower_bound(tmp);
				if(tvt != s[nowP].end()) {
					if(solved.empty() || !sameRank(*tvt, solved.back())) {
						rank += add + 1;
						add = 0;
					} else {
						add += 1;
					}
					solved.push_back(*tvt);
					res[tvt->id] = rank;
					L += tvt->z;
					nowx = tvt->x, nowy = tvt->y;
					s[nowP].erase(tvt);
					tag = true;	
				}
			}
		}
	}while(tag);
	for(int i = 0; i < n; i++) {
		cout << res[i] << ' ';
	}

	return 0;
}

I题 数的拆分

题面

做法

思考后发现,取 y1 = 2,y2 = 3,足够表示所有情况了。问题转变为 n 能否被表示为一个平方数和一个立方数的乘积。

对于单个数字。枚举 1e18 以内所有的立方数(只有 1e6 个),如果它(称为 p)能够整除 n ,且n/p 是一个平方数,那么答案是 yes。

这样我们能够在 1e6 规模的运算次数后算出一个 n 的答案,进一步考虑优化。若 n 能够被表示为一个平方数和一个立方数的乘积,则这个立方数和平方数,必然有一个小于 。也就是说枚举立方数只需要枚举到 1e3,枚举平方数需要枚举到 4e4 这样子就足够了。

然后我就不会做了,我只做了 30% 的部分分数。

// 历史版本(错误的说法,使用Pollard-Rho算法的时间复杂度是不对的):

据说使用一些质因数分解算法可以解决这道题。

分解质因数知乎,中文互联网高质量的问答社区和创作者聚集的原创内容平台,于 2011 年 1 月正式上线,以「让人们更好的分享知识、经验和见解,找到自己的解答」为品牌使命。知乎凭借认真、专业、友善的社区氛围、独特的产品机制以及结构化和易获得的优质内容,聚集了中文互联网科技、商业、影视、时尚、文化等领域最具创造力的人群,已成为综合性、全品类、在诸多领域具有关键影响力的知识分享社区和创作者聚集的原创内容平台,建立起了以社区驱动的内容变现商业模式。http://分解质因数

// 历史版本

// 学完正解后的版本:

历史版本假了,在瞎说。

正解在这个回答下有人已经说清楚了。

// 学完正解后的版本

部分分代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

bool check2(ll x) {
	ll v = sqrt(x);
	if((v + 1) * (v + 1) <= x) v += 1;
	if(v * v > x) v -= 1;
	return v * v == x;	
}
const ll N = (ll)1e6;
ll num[N + 5], sq[N + 5];

bool check3(ll x) {
	ll v = lower_bound(num + 1, num + N + 1, x) - num;
	return num[v] == x;	
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	for(ll i = 1; i <= N; i++) {
		num[i] = i * i * i;
		sq[i] = i * i;
	}
	
	int T;
	cin >> T;
	while(T--) {
		ll n;
		cin >> n;
		bool tag = false;
		ll z = sqrt(n) + 1;
		for(int i = 1; num[i] <= z; i++) {
			if(n % num[i] == 0 && check2(n / num[i])) {
				tag = true;
				break;
			}
		}
		if(!tag) {
			for(int i = 1; sq[i] <= z; i++) {
				if(n % sq[i] == 0 && check3(n / sq[i])) {
					tag = true;
					break;
				}
			}
		}
		if(tag) cout << "yes\n";
		else cout << "no\n";
	}
	
	return 0;
}

J题 推导部分和

题面

分析

不会做,听说有一些和这个很像的题,不过赛场上没时间做了,也没有思考,也没有做过原题,有空去补一补吧...

更新做法

E - Range SumsAtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.https://atcoder.jp/contests/abc238/tasks/abc238_e

在 atcoder 上有一道做法与本题一样的题目。

令前缀和数组 sum[i] 表示 a[1] + a[2] + ... + a[i] 的值。对于一个 [l, r, s],相当于给出了 sum[r] - sum[l - 1] = s 的信息。建一个森林,每个 [l, r ,s] 相当于在点 l - 1 和点 r 之间连了一条表示 sum[r] - sum[l - 1] = s 的边(代码实现上连两条值互为相反数的有向边更为合适)。

对于询问 [l, r],有解当且仅当 r 与 l - 1 同一棵树上时。对于每一棵树,任意指定一个根节点,假设它(root)的值(sum[root])为 0,从它开始 dfs 求出每个点(v)的值(sum[v]),这个过程通过预处理来实现。对于是否在同一棵树上,可以用给每个节点染色的方式实现。

之后的每次询问,只要判断它们是否颜色相同,若相同则答案为 sum[r] - sum[l - 1],否则无解。

今天就到这里了哈~

后续我还会发布更多的学习内容,希望大家可以持续关注,有什么问题可以给我留言或加入群基地一起讨论。

不管你是转行也好,初学也罢,进阶也可,如果你想学编程~

值得关注】我的【C/C++源码资料学习群】点击进入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值