A.jagged Swaps(思维)
题意:
给出一个包含nnn个数字的序列,每次可以选择一个同时大于左右两边相邻的数字,将这个数字与它右边的数字交换,问能否在经过若干次操作后使序列变为升序。
分析:
由于交换只能向后进行,且第一个元素无法向后交换(不存在左边的数字),而其他大的数字均可以通过交换到达自己的位置,因此只需要考虑开始时序列的第一个数字是否为1,如果是1,就是"YES",否则,就是"NO"。
hint:包含nnn个数字的序列恰好包含1∼n1 \sim n1∼n中每一个数字。
代码:
#include <bits/stdc++.h>
using namespace std;
int a[15];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (a[1] != 1) {
cout << "NO" << endl;
} else {
cout << "YES" << endl;
}
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
B.AB Flipping(思维)
题意:
给出一个长度为nnn且仅包含"AB"两种字符的字符串,每次可以选择一个下标iii,当字符串中第iii个字符为'A',且第i+1i + 1i+1个字符为'B',那么可以让第iii个字符和第i+1i + 1i+1个字符交换。
要求每个下标iii均只能选择一次,问最多可以进行多少次交换。
分析:
当出现连续的'B'前面有一个'A'时,这段连续区间上的'B'均可以向前交换一次,交换次数为这段连续的'B'的长度,而经过一次交换之后,由于每个下标只能被选择一次,那么仅有第一个'B'还能向前交换,可以认为这段连续的'B'交换完成后就只剩下开头这一个'B'了。使用变量维护还能向前交换的'B'的个数,从后往前遍历模拟即可。
代码:
#include <bits/stdc++.h>
using namespace std;
string s;
void solve() {
int n;
cin >> n >> s;
int cnt = 0, ans = 0;
for (int i = n - 1; i >= 0; i--) {
if (s[i] == 'B') {
cnt++;
} else {
ans += cnt;
cnt = min(cnt, 1);//如果当前没有遇到过B,就让cnt保持在0
}
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
C.Matching Arrays(贪心)
题意:
给出两个包含nnn个数字的数组aaa和bbb,这两个数组的美丽值为满足ai>bia_i > b_iai>bi的下标iii的个数。
题目会给出一个数字xxx,问能否对bbb重新排列,使得这两个数组的美丽值等于xxx。
分析:
虽然题目要求只能对bbb重排,但为了便于处理,两个数组都需要排序,同时为了记录数组aaa原本的数字位置,需使用结构体存储数据。
贪心:将aaa中最大的xxx个元素与bbb中最小的xxx个元素按大小次序进行比较,如果这部分元素无法构成xxx的美丽值,由于aaa中剩余元素更小,bbb中剩余元素更大,那么无论怎么交换元素,都无法使美丽值增加,此时本题无解。
检查:比较完aaa中最大的xxx个元素与bbb中最小的xxx个元素后,还需要考虑剩余的元素是否还会产生美丽值,同样采用按大小次序依次比较,如果产生美丽值那么同样表示本题无解。
输出:如果可以构造,那么需要根据记录的aaa中每个数字排序前的位置将对应的bbb数组元素输出。
代码:
#include <bits/stdc++.h>
using namespace std;
struct Node{
int val, id;
bool operator < (const Node &o) const {
return val < o.val;
}
}a[200005], b[200005];
int ans[200005];
void solve() {
int n, x;
cin >> n >> x;
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
a[i].id = i;
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
cin >> b[i].val;
b[i].id = i;
}
sort(b + 1, b + 1 + n);
for (int i = 1; i <= x; i++) {
if (a[i + n - x].val <= b[i].val) {//a中最大的x个与b中最小的x个对位比较
cout << "NO" << endl;
return;
}
ans[a[i + n - x].id] = b[i].val;//将当前b中元素放入对应的a中元素原本所在下标对应的位置上
}
for (int i = 1; i <= n - x; i++) {
if (a[i].val > b[i + x].val) {//剩余的n-x个元素对位比较
cout << "NO" << endl;
return;
}
ans[a[i].id] = b[i + x].val;
}
cout << "YES" << endl;
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
cout << endl;
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
D.Ones and Twos(思维,数据结构)
题意:
给出一个仅包含1,21,21,2的数组。
有qqq个询问,询问分以下两种情况:
-
"1 s",询问数组中能否找出一个子段和为sss -
"2 i v",将数组中第iii个数字修改为vvv
分析:
通过分析样例,可以发现以下规律:如果子段的左右端点数字均为1,那么可以组成任意值在1∼1 \sim1∼(子段数字之和)以内的数字。
根据以上规律,想要组成尽可能多的数字,那么选择的一定是最左和最右的两个111中间的子段(包含端点)。
然后需要根据以下情况进行分类讨论:
-
数组中存在111,这两个111之间的子段总和为sumsumsum
-
x≤sumx \le sumx≤sum,则可以组成
-
x>sumx \gt sumx>sum,分成以下两种情况:
-
xxx与sumsumsum奇偶性相同,为了尽可能使总和最大,一定会选择将选择的子段向左右扩散,且此时左右元素一定均为2,只要整个数组的数字总和可以达到xxx,那么就能组成xxx。
-
奇偶性不同时,可以删去子段一侧的111,再加上另一侧的222,看组成的子段数字总和能否到达xxx。
-
-
-
数组中不存在111,那么能组成的只有偶数,且能组成的偶数xxx的值要在数组中数字总和的范围内。
hint:可以通过setsetset对所有111所在的位置(下标)进行维护,通过∗(begin())*(begin())∗(begin())和∗(−−end())*(--end())∗(−−end())(end()函数返回的是最后一个元素的下一个迭代器,需要通过前自减得到最后一个元素的迭代器)来获得集合中最小和最大的元素。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, q, a[100005], cnt;
set<int> S;
bool check(int x) {
if (S.empty()) {//数组中没有1
if (x % 2 == 1) return false;
if (n * 2 < x) return false;
return true;
}
int first = *S.begin(), last = *(--S.end());//获得最前和最后的1所在的下标
int sum = (last - first + 1) * 2 - S.size();//将区间内所有的数视为2,计算出总和,减去1的数量,就是该子段的数字总和
if (sum >= x) return true;//被1包围的子段已经能组成x了
if (x % 2 == sum % 2) {
int add = n - (last - first + 1);//计算出未被加上的2的数量
if (sum + add * 2 >= x) return true;
} else {
int add = max(n - last, first - 1);//计算左右两边最多有多少个2
if (sum - 1 + add * 2 >= x) return true;
}
return false;
}
void solve() {
S.clear();
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] == 1) {
S.insert(i);
cnt++;
}
}
while (q--) {
int op;
cin >> op;
if (op == 1) {
int x;
cin >> x;
if (check(x)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
} else {
int i, v;
cin >> i >> v;
if (a[i] == 1) S.erase(i);
a[i] = v;
if (a[i] == 1) S.insert(i);
}
}
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
E. Permutation Sorting(数据结构)
题意:
给一个大小为nnn的排列aaa,如果满足ai=ia_i=iai=i,那么称下标iii是良好的,每一秒,将所有不好的下标iii取出来作为一个子序列,保证下标iii是升序的。
假设这个序列为s1−sks_1-s_ks1−sk,那么对1−k1-k1−k的每个iii,使得si%k+1=sis_{i\%k+1}=s_isi%k+1=si。现在对于每一个下标iii,询问下标iii第一次变好的时间。
分析:
我们假设iii与aia_iai之间存在一条边,并且将每组[i,ai][i,a_i][i,ai]看作一个区间,那么对于每一个下标iii,它的答案为:区间长度−1−-1-−1−在它之前的并且被它完全包含的区间。
我们可以用二维数点来维护有哪些区间被当前处理的区间包含,将左端点当作xxx坐标,右端点当作yyy坐标,类似于求二维前缀和。考虑到这里是环形数组,可以倍长简化操作。
代码:
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 1e6 + 5;
int n;
int a[N];
int ans[N];
int id[N];
int tr[2 * N];
int vis[N];
void add(int x, int k) {
for (; x <= n; x += (x & -x))
tr[x] += k;
}
int ask(int x, int res = 0) {
for (; x; x -= (x & -x))
res += tr[x];
return res;
}
int qry(int l, int r) {
if (l <= r)
return ask(r) - ask(l - 1);
else
return ask(n) - qry(r, l - 1);
}
void Init() {
for (int i = 1; i <= n; i++)
tr[i] = 0;
for (int i = 1; i <= n; i++)
ans[i] = 0, vis[i] = 0;
}
int main() {
int t = 1;
cin >> t;
while (t--) {
cin >> n;
Init();
for (int i = 1; i <= n; i++) {
cin >> a[i];
id[a[i]] = i;
}
for (int i = 1; i <= 2 * n; i++) {
int x = (i - 1) % n + 1;
if (vis[x]) {
ans[x] = qry(id[x] + 1, x) + 1;
add(id[x], -1);
vis[x] = 0;
}
if (a[x] != x) {
add(x, 1);
vis[a[x]] = 1;
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
cout << endl;
}
return 0;
}
以下学习交流QQ群,群号: 546235402,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

文章讲述了四个编程问题,涉及数组操作、字符串翻转、数组美丽值匹配和特殊数字数组的查询。这些问题主要考察了思维策略、贪心算法以及数据结构的应用。

513

被折叠的 条评论
为什么被折叠?



