A. 简单运算
每升一级强度变为原来的 32 32 32 倍,升 A − B A - B A−B 级强度变为原来的 3 2 A − B 32^{A - B} 32A−B 倍
A − B A - B A−B 最大是 6 6 6,答案最大是 3 2 6 = 2 30 32^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. 暴力判断
先判断两个字符串是否相等,如果相等直接输出 Y e s Yes Yes
否则 O ( ∣ S ∣ ) O(|S|) O(∣S∣) 枚举相邻两个字符交换的位置,再 O ( ∣ S ∣ ) O(|S|) O(∣S∣) 进行比较,时间复杂度 O ( ∣ S ∣ 2 ) O(|S|^2) O(∣S∣2)
#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 ≤ 1 0 9 1\leq N\leq 10^9 1≤N≤109,所以从字符串角度来看 N N N 最多有 9 9 9 位(至少两个位置不为 0 0 0)
我们可以 O ( 2 ∣ N ∣ ) O(2^{|N|}) O(2∣N∣) 枚举 u , v u, v u,v 分别取了哪些数
对于取得的数,按高位到低位从大到小排一定是最优的
其实没有必要考虑前缀 0 0 0 的情况,因为
- 0 0 0 一定在我构造的数的最后
- 如果出现只有 0 0 0 构成的情况,这样算也没有问题,因为是 0 0 0,不会对最终的答案造成贡献
时间复杂度: O ( 2 ∣ N ∣ × N ) O(2^{|N|}\times N) O(2∣N∣×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+B−1] 他可以抽象为两个点:
- 在 x = A x = A x=A 时人数 + 1 +1 +1
- 在 x = A + B − 1 x = A + B - 1 x=A+B−1 时人数 − 1 -1 −1
所以大致的思路是根据端点排序再从左向右扫一边就行
首先是将区间化为左开右闭的形式,即 [ A , A + B ) [A, A + B) [A,A+B),并将其分割为两个点 + 1 +1 +1 点和 − 1 -1 −1 点
将点排序,需要注意的是,对于同一个点, − 1 -1 −1 点需要在 + 1 +1 +1 点之前处理
处理一个 + 1 +1 +1 或 − 1 -1 −1 点时,需要将其前一段的贡献记录一下
复杂度: O ( N log 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. 逆序数变种
这是一个逆序数变种的题,下面来分析
对于 A 1 ′ ≤ A k ′ A_1'\leq A_k' A1′≤Ak′ 这一条件,可以看作:
- 选定开头 A 1 ′ A_1' A1′ 和末尾 A k ′ A_k' Ak′,中间的数任意取
- 比如取 A 1 ′ = A u , A k ′ = A v A_1' = A_u, A_k' = A_v A1′=Au,Ak′=Av,那么这个状态的总情况数为 2 v − u − 1 2^{v - u - 1} 2v−u−1
所以初步的想法是枚举所有 A 1 ′ A_1' A1′ 和 A k ′ A_k' Ak′ 计算贡献,这样的复杂度是 O ( N 2 ) O(N^2) O(N2),显然无法满足条件
我们可以从逆序数的角度来看这个题:
- 传统的逆序数:存在 i , j , A i > A j i, j, A_i > A_j i,j,Ai>Aj,对于答案的贡献为 1 1 1
- 现在 存在: i , j , A i ≤ A j i, j, A_i \leq A_j i,j,Ai≤Aj,对于答案的贡献为 2 j − i − 1 2^{j - i - 1} 2j−i−1
逆序数,正序数的区别自然不影响,那么我们从逆序数的做法来入手
- 数状数组/线段树,但是统计区间贡献的时候每个点的贡献不再是 1 1 1,而是需要维护一些特殊的差分项来实现,需要推公式构造出这样的形式。这种做法也是题解中比较主流的做法,需要扎实的数状数组和数学基础
- 归并排序,我们可以在归并排序中统计答案,如下
这里数值计算只与位置有关,值只有比大小的作用
所以我们进行一下操作,来保留有用信息
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;
}
下面我们对 i d id id 数组进行归并排序即可,可以证明这样做是正确的
在归并排序中的 m e r g e merge merge 操作中统计答案
- 如果当前是前一段元素的第一个,此元素跟后一段的所有元素都产生贡献
- 否则当前是后一段元素的第一个,不产生贡献
其中需要逆元和幂运算,所以需要事先 O ( N ) O(N) O(N) 打表
具体细节见代码中的 m e r g e merge merge 函数
时间复杂度: O ( N log 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;
}