AtCoder Beginner Contest 221 题解

本文探讨了五个编程问题,涉及简单运算的指数增长、字符串比较、贪心策略优化、一维扫描线算法和逆序数的变种解决方案。通过实例和优化技巧,展示了如何利用不同算法技术应对不同复杂度的问题。

A. 简单运算

每升一级强度变为原来的 323232 倍,升 A−BA - BAB 级强度变为原来的 32A−B32^{A - B}32AB

A−BA - BAB 最大是 666,答案最大是 326=23032^6 = 2^{30}326=230,int 类型即可

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

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int a, b, ans = 1;
	cin >> a >> b;
	for (int i = 1; i <= a - b; i++) {
		ans <<= 5;
	}
	cout << ans << '\n';
	return 0;
}

B. 暴力判断

先判断两个字符串是否相等,如果相等直接输出 YesYesYes

否则 O(∣S∣)O(|S|)O(S) 枚举相邻两个字符交换的位置,再 O(∣S∣)O(|S|)O(S) 进行比较,时间复杂度 O(∣S∣2)O(|S|^2)O(S2)

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

string s, t;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> s >> t;
	if (s == t) cout << "Yes\n";
	else {
		int n = s.size();
		for (int i = 0; i + 1 < n; i++) {
			swap(s[i], s[i + 1]);
			if (s == t) {
				cout << "Yes\n";
				return 0;
			}
			swap(s[i], s[i + 1]);
		}
		cout << "No\n";
	}
	return 0;
}

C. 暴力 + 贪心

因为 1≤N≤1091\leq N\leq 10^91N109,所以从字符串角度来看 NNN 最多有 999 位(至少两个位置不为 000

我们可以 O(2∣N∣)O(2^{|N|})O(2N) 枚举 u,vu, vu,v 分别取了哪些数

对于取得的数,按高位到低位从大到小排一定是最优的

其实没有必要考虑前缀 000 的情况,因为

  • 000 一定在我构造的数的最后
  • 如果出现只有 000 构成的情况,这样算也没有问题,因为是 000,不会对最终的答案造成贡献

时间复杂度:O(2∣N∣×N)O(2^{|N|}\times N)O(2N×N)

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

int cal(vector<int>& nums) {
	int ret = 0;
	sort(nums.begin(), nums.end());
	for (int i = nums.size() - 1; i >= 0; i--) {
		ret = 10 * ret + nums[i];
	}
	return ret;
}

string s;

int main(void) {
	vector<int> a, b;
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> s;
	int n = s.size(), ans = 0, now;
	for (int i = 0; i < (1 << n); i++) {
		a.clear(); b.clear();
		for (int j = 0; j < n; j++) {
			if (i >> j & 1) a.push_back(s[j] - '0');
			else b.push_back(s[j] - '0');
		}
		ans = max(ans, cal(a) * cal(b));
	}
	cout << ans << '\n';
	return 0;
}

D. 一维扫描线

此题的思路类似与扫描线

不同的是它只需要维护一个维度,不需要用线段树维护第二维

所以相对来说简单很多

为什么可以说他是扫描线呢?

因为对于一个 [A,A+B−1][A, A + B - 1][A,A+B1] 他可以抽象为两个点:

  • x=Ax = Ax=A 时人数 +1+1+1
  • x=A+B−1x = A + B - 1x=A+B1 时人数 −1-11

所以大致的思路是根据端点排序再从左向右扫一边就行

首先是将区间化为左开右闭的形式,即 [A,A+B)[A, A + B)[A,A+B),并将其分割为两个点 +1+1+1 点和 −1-11

将点排序,需要注意的是,对于同一个点,−1-11 点需要在 +1+1+1 点之前处理

处理一个 +1+1+1−1-11 点时,需要将其前一段的贡献记录一下

复杂度:O(Nlog⁡N)O(N\log N)O(NlogN)

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

const int N = 2e5 + 5;
vector<pair<int, int>> vt;
int ans[N], n;

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1, a, b; i <= n; i++) {
		cin >> a >> b;
		vt.push_back({a, 1});
		vt.push_back({a + b, -1});
	}
	sort(vt.begin(), vt.end());
	int cnt = 1, prex = vt[0].first;
	for (int i = 1; i < (int) vt.size(); i++) {
		pair<int, int> now = vt[i];
		ans[cnt] += now.first - prex;
		cnt += now.second;
		prex = now.first;
	}
	for (int i = 1; i <= n; i++) {
		cout << ans[i] << ' ';
	}
	cout << '\n';
	return 0;
}

E. 逆序数变种

这是一个逆序数变种的题,下面来分析

对于 A1′≤Ak′A_1'\leq A_k'A1Ak 这一条件,可以看作:

  • 选定开头 A1′A_1'A1 和末尾 Ak′A_k'Ak,中间的数任意取
  • 比如取 A1′=Au,Ak′=AvA_1' = A_u, A_k' = A_vA1=Au,Ak=Av,那么这个状态的总情况数为 2v−u−12^{v - u - 1}2vu1

所以初步的想法是枚举所有 A1′A_1'A1Ak′A_k'Ak 计算贡献,这样的复杂度是 O(N2)O(N^2)O(N2),显然无法满足条件

我们可以从逆序数的角度来看这个题:

  • 传统的逆序数:存在 i,j,Ai>Aji, j, A_i > A_ji,j,Ai>Aj,对于答案的贡献为 111
  • 现在 存在:i,j,Ai≤Aji, j, A_i \leq A_ji,j,AiAj,对于答案的贡献为 2j−i−12^{j - i - 1}2ji1

逆序数,正序数的区别自然不影响,那么我们从逆序数的做法来入手

  • 数状数组/线段树,但是统计区间贡献的时候每个点的贡献不再是 111,而是需要维护一些特殊的差分项来实现,需要推公式构造出这样的形式。这种做法也是题解中比较主流的做法,需要扎实的数状数组和数学基础
  • 归并排序,我们可以在归并排序中统计答案,如下

这里数值计算只与位置有关,值只有比大小的作用

所以我们进行一下操作,来保留有用信息

for (int i = 1; i <= n; i++) {
    cin >> a[i].first; a[i].second = i;
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) {
    id[i] = a[i].second;
}

下面我们对 ididid 数组进行归并排序即可,可以证明这样做是正确的

在归并排序中的 mergemergemerge 操作中统计答案

  • 如果当前是前一段元素的第一个,此元素跟后一段的所有元素都产生贡献
  • 否则当前是后一段元素的第一个,不产生贡献

其中需要逆元和幂运算,所以需要事先 O(N)O(N)O(N) 打表

具体细节见代码中的 mergemergemerge 函数

时间复杂度:O(Nlog⁡N)O(N\log N)O(NlogN)

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

const int N = 3e5 + 5, mod = 998244353;
int n, id[N], tmp[N], pw2[N], invpw2[N], ans;
pair<int, int> a[N];

void merge(int vv[], int l, int r) {
	int mid = (l + r) >> 1;
	// (l, mid), (mid + 1, r)
	int u = 0, v = 0;
	for (int i = l; i <= mid; i++) {
		u += pw2[vv[i]], u %= mod;
	}
	for (int i = mid + 1; i <= r; i++) {
		v += pw2[vv[i]], v %= mod;
	}
	int p1 = l, p2 = mid + 1, now = l;
	while (p1 <= mid && p2 <= r) {
		if (vv[p1] < vv[p2]) {
			tmp[now++] = vv[p1];
			ans += 1ll * v * invpw2[vv[p1] + 1] % mod, ans %= mod;
			u -= pw2[vv[p1]], u %= mod;
			p1++;
		}
		else {
			tmp[now++] = vv[p2];
			v -= pw2[vv[p2]], v %= mod;
			p2++;
		}
	}
	while (p1 <= mid) tmp[now++] = vv[p1++];
	while (p2 <= r) tmp[now++] = vv[p2++];
	for (int i = l; i <= r; i++) {
		vv[i] = tmp[i];
	}
}

void merge_sort(int vv[], int l, int r) {
	if (l == r) return;
	int mid = (l + r) >> 1;
	merge_sort(vv, l, mid);
	merge_sort(vv, mid + 1, r);
	merge(vv, l, r);
}

int main(void) {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	pw2[0] = 1, invpw2[0] = 1;
	int inv2 = (mod + 1) >> 1;
	for (int i = 1; i < N; i++) {
		pw2[i] = 1ll * pw2[i - 1] * 2 % mod;
		invpw2[i] = 1ll * invpw2[i - 1] * inv2 % mod;
	}
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].first; a[i].second = i;
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i++) {
		id[i] = a[i].second;
	}
	merge_sort(id, 1, n);
	ans += mod; ans %= mod;
	cout << ans << '\n';
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值