title: Codeforces Round#744 (Div. 3) date: 2021-09-29 12:40:22 updated: 2021-09-29 12:40:22 categories: ACM&算法 tag:
自从退役之后,打了三个月的工,然后再来打这一场 Div3,庆幸自己还能打打,在最后还剩 4 分钟的时候 A 掉了 G 题,终于在比赛期间 AK
给你一个字符串,仅还有 'A', 'B', 'C'
三个字符,每次可以同时删除任意两个 'A', 'B'
,也可以同时删除两个 'B', 'C'
。判断一个字符串能过上述操作变为空字符串
统计了一下所有字符串中每个字符串的数量,然后若 'B'
的数量和 'A'
和 'C'
的数量之和相同,则 OK
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
string str;
cin >> str;
int a = 0, b = 0, c = 0;
for (auto &item : str) {
switch (item) {
case 'A':
a++;
break;
case 'B':
b++;
break;
case 'C':
c++;
break;
}
}
cout << (a + c == b ? "YES" : "NO") << endl;
}
}
一个字符串,每次允许选择其中一个区间,对这个区间进行移位运算,使得这个数组最终有序,使用此操作不能超过整个数组长度次数
这已经把插入排序写在脸上了
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (int i = 0; i < n; ++i) cin >> data[i];
vector<pair<int, int>> ans;
for (int i = 1; i < n; ++i) {
int l = i + 1, r = i + 1;
for (int j = i - 1; j >= 0; --j) {
if (data[j] > data[j + 1]) {
l--;
swap(data[j], data[j + 1]);
}
else break;
}
if (l != r) ans.emplace_back(l, r);
}
cout << ans.size() << endl;
for (auto &item: ans) {
cout << item.first << ' ' << item.second << " " << item.second - item.first << endl;
}
}
}
一个矩形网格,在上面画 'V'
字形,问,当前对矩形网格上,是否是可以通过画若干个至少为 'k'
大的 'V'
来满足
首先所有 'V'
的特点是最下面的点,每个 'V'
都可以用最下面的点来标记 'V'
,而其两臂则可以有多长就多长即可。所以可以很轻松得出,应该从下往上遍历来解决问题
如果从下向上遍历,那么若遇到一个 '*'
点,有可能是之前 'V'
的臂,也有可能是新的 'V'
,同时也可以是两者的结合。所以需要一个标记数组,表示每个点是否已经被下面的 'V'
给画过了,若没有,则这里必定是 'V'
的起点。
但是若为画过,则需要同时考虑两种情况
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, m, k;
cin >> n >> m >> k;
vector<string> data(n);
vector<vector<bool>> vis(n);
for (int i = 0; i < n; ++i) {
cin >> data[i];
vis[i].resize(m, false);
}
bool flag = true;
auto findCell = [&](int x, int y) {
int cur = -1;
for (int i = 0; i < n; ++i) {
bool left = x >= i && y >= i && data[y - i][x - i] == '*';
bool right = x + i < m && y >= i && data[y - i][x + i] == '*';
if (left && right) {
cur++;
vis[y - i][x - i] = true;
vis[y - i][x + i] = true;
} else break;
}
if (cur < k) flag = false;
};
auto tryCell = [&](int x, int y) {
int cur = -1;
for (int i = 0; i < n; ++i) {
bool left = x >= i && y >= i && data[y - i][x - i] == '*';
bool right = x + i < m && y >= i && data[y - i][x + i] == '*';
if (left && right) {
cur++;
} else break;
}
if (cur >= k) {
for (int i = 0; i < cur + 1; ++i) {
vis[y - i][x - i] = true;
vis[y - i][x + i] = true;
}
}
};
for (int i = n - 1; i >= 0; --i)
for (int j = 0; j < m; ++j)
if (data[i][j] == '*') {
if (!vis[i][j]) findCell(j, i);
else tryCell(j, i);
}
cout << (flag ? "YES" : "NO") << endl;
}
}
有 $n$ 堆石头,每堆石头有若干数量,每次从两堆不同堆石头中取出各一个,如何取使得最后所有堆的石头和最少
第一反应过来以为是背包问题,就是普通的分为两组然后尽可能均分。但是很快意识到不对,因为可以一个人在两堆中变换。然后就简单了,简单的不断取出最大的两堆,各取一个,直到不能取出两个即可
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
priority_queue<pair<int, int>> prq;
for (int i = 0; i < n; ++i) {
int tmp;
cin >> tmp;
if (tmp == 0) continue;
prq.push({tmp, i});
}
vector<pair<int, int>> ans;
while (prq.size() >= 2) {
auto a = prq.top();
prq.pop();
auto b = prq.top();
prq.pop();
ans.emplace_back(a.second, b.second);
if (a.first > 1) prq.push({a.first - 1, a.second});
if (b.first > 1) prq.push({b.first - 1, b.second});
}
cout << ans.size() << endl;
for (auto &item : ans) cout << item.first + 1 << ' ' << item.second + 1 << endl;
}
}
一个双向队列,按照一定顺序往其中插入一组值,在已知接下来要插入的值的顺序后,如何确定每一次插入队列前面还是后面,使得整个序列的字典序最小
设计的逻辑很简单,其实每次插入时,若比第一个值大,那么插入到后面,否则一定会使整体值增加,反正则插入到前面即可
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (int i = 0; i < n; ++i) cin >> data[i];
list<int> res;
res.push_back(data[0]);
for (int i = 1; i < n; ++i) {
if (res.front() > data[i]) res.push_front(data[i]);
else res.push_back(data[i]);
}
for (auto &item : res) {
cout << item << ' ';
}
cout << endl;
}
}
和上一题差不多的同时,这次需要的是使得逆序对尽可能少
贪心解决了,每次插入的时候,若插入到最前面产生的逆序对数量少于最后面,则插入到前面,否则后面。而计算数量,应该是很久没训练了,一下子只能想到线段树,所以就直接上一个动态开点的线段树解决了
#include "bits/stdc++.h"
using namespace std;
const int N = 3e6;
const int L = -1e9 - 10;
const int R = 1e9 + 10;
struct SegTree {
int s[N], l[N], r[N];
int tot;
void init() {
tot = 1;
s[0] = 0;
l[0] = -1;
r[0] = -1;
}
int newNode() {
s[tot] = 0;
l[tot] = -1;
r[tot] = -1;
return tot++;
}
int lc(int x) {
if (l[x] == -1)
l[x] = newNode();
return l[x];
}
int rc(int x) {
if (r[x] == -1)
r[x] = newNode();
return r[x];
}
void up(int x) {
s[x] = 0;
if (l[x] != -1) s[x] += s[l[x]];
if (r[x] != -1) s[x] += s[r[x]];
}
void add(int x, int cur, int ll, int rr) {
if (ll == rr) {
s[cur]++;
return;
}
int mid = (ll + rr) >> 1;
if (x <= mid) add(x, lc(cur), ll, mid);
else add(x, rc(cur), mid + 1, rr);
up(cur);
}
int query(int x, int y, int cur, int ll, int rr) {
if (ll == x && rr == y) {
return s[cur];
}
if (s[cur] == 0) return 0;
int mid = (ll + rr) >> 1;
if (y <= mid) {
return query(x, y, lc(cur), ll, mid);
} else if (x > mid) {
return query(x, y, rc(cur), mid + 1, rr);
} else {
return query(x, mid, lc(cur), ll, mid) + query(mid + 1, y, rc(cur), mid + 1, rr);
}
}
} segTree;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (int i = 0; i < n; ++i) cin >> data[i];
long long ans = 0;
segTree.init();
segTree.add(data[0], 0, L, R);
for (int i = 1; i < n; ++i) {
int left = segTree.query(data[i] + 1, R, 0, L, R);
int right = segTree.query(L, data[i] - 1, 0, L, R);
ans += min(left, right);
segTree.add(data[i], 0, L, R);
}
cout << ans << endl;
}
}
给你一个 01
字符串,每次进行对此字符串的某个移位运算后的值进行 AND
运算的,直到此字符串不再改变,需要多少次才能使得整个字符串变为纯 0
的字符串
根据移位操作,建图,然后在拓扑,找出最长链就行了,若不能完整拓扑,则不能
#include "bits/stdc++.h"
using namespace std;
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n, d;
cin >> n >> d;
vector<int> data(n);
for (int i = 0; i < n; ++i) cin >> data[i];
bool flag = false;
for (int i = 0; i < n; ++i) {
if (data[i] == 1) {
flag = true;
break;
}
}
if (!flag) {
cout << 0 << endl;
continue;
}
vector<int> to(n, -1);
vector<bool> deg(n, false);
for (int i = 0; i < n; ++i) {
int nxt = (i + n - d) % n;
if (data[i] == 1 && data[nxt] == 1) {
to[i] = nxt;
deg[nxt] = true;
}
}
queue<pair<int, int>> q;
for (int i = 0; i < n; ++i) if (!deg[i]) q.push({i, 0});
int ans = 0;
int vis = 0;
while (!q.empty()) {
auto cur = q.front();
q.pop();
vis++;
ans = max(ans, cur.second + 1);
if (to[cur.first] == -1) continue;
deg[to[cur.first]] = false;
q.push({to[cur.first], cur.second + 1});
}
if (vis == n)
cout << ans << endl;
else cout << "-1" << endl;
}
}
有 $n$ 段线段,首尾相连,连接处可以折叠,求出折叠后,这些线段占用的最小总长度
借用一下数据量并不大的特点,可以直接暴力找所有可能的情况。创建一个布尔数组,若此处为 true 则表示可以从这里开始,否则不能。通过滚动的方式进行 dp 最后找到任意一处为 true 则为成功。
当然此方法仅适合用于 check,所以加一个二分就能解决了
#include "bits/stdc++.h"
using namespace std;
bool vis[2][3100];
int main() {
int _;
cin >> _;
for (int ts = 0; ts < _; ++ts) {
int n;
cin >> n;
vector<int> data(n);
for (int i = 0; i < n; ++i) cin >> data[i];
int l = 0, r = 2000;
auto cal = [&](int len) {
int cur = 0, nxt = 1;
memset(vis[nxt], true, sizeof(vis[nxt]));
for (auto &item: data) {
memset(vis[cur], false, sizeof(vis[cur]));
for (int i = 0; i < len; ++i) {
if (vis[nxt][i]) {
if (i - item >= 0) vis[cur][i - item] = true;
if (i + item < len) vis[cur][i + item] = true;
}
}
swap(cur, nxt);
}
for (int i = 0; i < len; ++i) if (vis[nxt][i]) return true;
return false;
};
while (l + 3 < r) {
int mid = (l + r) >> 1;
if (cal(mid + 1)) r = mid;
else l = mid;
}
for (int i = l + 5; i >= l - 5; --i) {
if (!cal(i + 1)) {
cout << i + 1 << endl;
break;
}
}
}
}