AtCoder ABC 216
A. Signed Difficulty
解题思路
签到
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
string s;
cin >> s;
string x = s.substr(0, s.find('.'));
int y = stoi(s.substr(s.find('.') + 1));
if (y <= 2) {
x += '-';
} else if (y <= 6) {
} else {
x += '+';
}
cout << x << '\n';
return 0;
}
B. Same Name
解题思路
签到
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
map<pair<string, string>, int> mp;
int n;
cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) {
string s, t;
cin >> s >> t;
ans += mp[{s, t}];
mp[{s, t}]++;
}
cout << (ans ? "Yes" : "No") << '\n';
return 0;
}
C - Many Balls
题意
给你一个数 \(N\),你一开始手中的数字是 \(0\),你每次可以选择给这个数 \(+1\) 或者 \(\times 2\)
请你构造出一个长度不超过 \(120\) 的操作字符串,使得按照这种操作可以使你手中的数变成 \(N\)。
解题思路
我们发现 \(+1\) 和 \(\times 2\) 操作,和我们二进制转十进制十分像,故我们想到把 \(N\) 拆成二进制考虑即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
LL n;
cin >> n;
for (int i = 60; i >= 0; i--) {
cout << 'B';
if (n >> i & 1) {
cout << 'A';
}
}
return 0;
}
D. Pair of Balls
题意
给你 \(m\) 个放球的筒,一共有 \(2N\) 个球,\(N\) 种颜色,每种颜色的球恰好有 \(2\) 个,现在你可以
每次从 \(m\) 个球筒中任意两个不为空的球筒的顶部取球,若这两个球颜色一样,则把他们取走,否则不能取走,请问你最后可以按照这样的操作把所有球取出来吗?
解题思路
这里需要把问题转化为图论问题,我们可以把 \(n\) 种颜色看成点,然后球筒内部的顺序看成边
问题就变成了一共拓扑排序问题;还有一种思路就是,直接考虑 dfs,考虑连锁反应,每有一种颜色
出现了两次,就取出,加入下一个位置的球,递归下去,看看最后是否取完。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<int>> a(m + 1), b(n + 1);
int cnt = n;
function<void(int)> dfs = [&](int u) {
int w = a[u].back();
b[w].push_back(u);
if (b[a[u].back()].size() == 2) {
cnt--;
for (auto v : b[w]) {
a[v].pop_back();
if (!a[v].empty()) {
dfs(v);
}
}
}
};
for (int i = 1; i <= m; i++) {
int k;
cin >> k;
for (int j = 1; j <= k; j++) {
int x;
cin >> x;
a[i].push_back(x);
}
reverse(a[i].begin(), a[i].end());
dfs(i);
}
cout << (cnt == 0 ? "Yes" : "No") << '\n';
return 0;
}
E. Amusement Park
题意
你有 \(n\) 个景点,每个景点都有一个快乐值 \(a_i\),你有 \(k\) 次访问机会,每次你访问一个景点
之后,会使得该景点的快乐值降低一点,即令 \(a_i=a_i-1\),问你最终能获得的最大快乐值是多少?
数据范围
\(1\leq n \leq 10^5\),\(1\leq k, a_i \leq 2\times 10^9\)
解题思路
首先我们有个贪心思路就是,每次都取快乐值最大的,我们可以用个堆动态维护,但是我们发现
\(k\) 太大了,我们显然不能直接用堆模拟。我们考虑一个临界值,即最后我取的那个数的值是多少?
我们发现他必定会取前 \(k\) 大,我们可以先找到第 \(k\) 大的数是啥,这是一个经典的二分问题
我们只要找到这个 \(x\),然后算一下价值,最后注意处理一下多加的一些 \(x\),减去即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef __int128 i128;
typedef pair<int, int> PII;
const int N = 1e5 + 10;
int a[N];
template <typename T> void print(T x) {
if (x < 0) { putchar('-'); print(x); return ; }
if (x >= 10) print(x / 10);
putchar((x % 10) + '0');
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
LL l = 1, r = 2e9;
auto calc = [&](LL x) {
LL res = 0;
for (int i = 1; i <= n; i++) {
res += max(0LL, a[i] - x + 1);
}
return res;
};
while (l < r) {
LL mid = (l + r + 1) >> 1;
if (calc(mid) >= k) l = mid;
else r = mid - 1;
}
i128 ans = 0;
LL res = 0;
for (int i = 1; i <= n; i++) {
int m = max(0LL, a[i] - l + 1);
res += m;
ans += (l + a[i]) * m / 2;
}
ans -= 1LL * l * max(0LL, res - k);
print(ans);
putchar('\n');
return 0;
}
F. Max Sum Counting
题意
给你两个序列 \(A\) 和 \(B\),现在请你选出一个 \(\{1,2,..,N\}\) 的非空子集 \(S\),然后满足如下条件
- \(max_{i\in S}A_i \geq \sum_{i\in S}B_i\)
请问你可以选出多少种不同的子集,请你输出答案对 \(998244353\) 取模的结果。
数据范围
\(1\leq N,A_i,B_i\leq 5000\)
解题思路
首先观察数据范围以及计数类问题,我们很容易想到 DP ,由于涉及到最大值,我们考虑先让序列
按 \(A_i\) 的值从小到大排序,那么我们有个想法就是,我们统计答案,只要枚举我们最后一个选的
\(A_i\) 在哪即可。我们不难想到背包 DP,我们考虑 \(dp[i][j]\) 表示考虑前 \(i\) 个数,当且选的数
的 \(B_i\) 之和为 \(j\),注意这里 \(j\) 最大不会超过 \(5000\),因为一旦超过五千,就找不到一个合法的最大值的 \(A_i\) 满足条件。故我们只要按照 \(01\) 背包 的转移,枚举每一个位置选还是不选,如果选择就统计 \(ans\)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int mod = 998244353, N = 5010;
int dp[N][N];
PII c[N];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> c[i].first;
}
for (int i = 1; i <= n; i++) {
cin >> c[i].second;
}
sort(c + 1, c + 1 + n);
int ans = 0;
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < N; j++) {
auto [a, b] = c[i];
dp[i][j] = dp[i - 1][j];
if (j >= b) {
if (j <= a) {
ans = (ans + dp[i - 1][j - b]) % mod;
}
dp[i][j] = (dp[i][j] + dp[i - 1][j - b]) % mod;
}
}
}
cout << ans << '\n';
return 0;
}
G. 01Sequence
题意
给你 \(m\) 个限制,每个限制表示 \([l_i,r_i]\) 之间至少 \(x\) 个 \(1\),现在请你构造一个长度为 \(n\)
的 \(01\) 序列,使得它满足所有 \(m\) 个限制,且要求序列中 \(1\) 的个数尽可能少。
数据范围
\(1\leq n \leq 2\times 10^5\),\(1\leq m \leq (2\times 10^5, n(n+1) / 2)\)
\(1\leq 1\leq l_i \leq r_i \leq n\),\(1\leq x_i \leq r_i - l_i + 1\)
解题思路
本题有两种思路:
(1) 差分约束
我们考虑前缀和,令 \(s_i\) 表示前 \(i\) 个位置 \(1\) 的个数之和,那么每个限制相当于 \(s_r - s_{l - 1} \geq x\)
而且题目需要我们构造一组解,且要 \(1\) 个数尽可能少,我们就可以想到这是差分约束的经典应用,我们只要按照如下几个限制关系建图:
- \(s_r - s_{l - 1} \geq x\)
- \(0\leq s_i - s_{i - 1} \leq 1\)
- \(s_i \geq 0\)
- \(s_0 = 0\)
然后跑最长路,即可求出所有 \(s_i\) 的最小值,然后就可以知道每个位置的值即为:\(s_i-s_{i-1}\)
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
vector<PII> g[N];
int d[N];
bool st[N];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
while (m--) {
int l, r, x;
cin >> l >> r >> x;
//s[r] - s[l - 1] >= x
//s[r] - s[l - 1] <= r - l + 1
//s[r] + l - r - 1 <= s[l - 1]
g[l - 1].push_back({r, x});
g[r].push_back({l - 1, l - r - 1});
}
g[n + 1].push_back({0, 0});
g[0].push_back({n + 1, 0});
//s[i] >= s[i - 1]
//s[i] - s[i - 1] <= 1
for (int i = 1; i <= n; i++) {
//s[i] >= 0
g[n + 1].push_back({i, 0});
g[i - 1].push_back({i, 0});
g[i].push_back({i - 1, -1});
}
queue<int> q;
memset(d, -0x3f, sizeof d);
d[n + 1] = 0;
q.push(n + 1);
st[n + 1] = true;
while (q.size()) {
int u = q.front();
q.pop();
st[u] = false;
for (auto [v, w] : g[u]) {
if (d[v] < d[u] + w) {
d[v] = d[u] + w;
if (!st[v]) {
st[v] = true;
q.push(v);
}
}
}
}
for (int i = 1; i <= n; i++) {
cout << d[i] - d[i - 1] << " \n"[i == n];
}
return 0;
}
(2) 贪心 + 并查集 + 树状数组
首先我们可以考虑先对限制按右端点从小到大排序,然后对于一个限制 \([l_i,r_i]\) 每次贪心的先尽可能靠右放,这样可以让后面的限制所放的 \(1\) 变少,故我们可以采用单链表式的并查集,维护每个位置往左第一个能放的位置,
然后每次用树状数组单点更新已经放 \(1\) 的位置,然后区间查询 \(1\) 的个数。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int p[N], ans[N];
array<int, 3> seg[N];
int c[N];
void updata(int x, int k) {
for (int i = x; i < N; i += i & -i) {
c[i] += k;
}
}
int ask(int x) {
int res = 0;
for (int i = x; i; i -= i & -i) {
res += c[i];
}
return res;
}
int find(int x) {
return x == p[x] ? x : p[x] = find(p[x]);
}
bool cmp(array<int, 3> &x, array<int, 3> &y) {
return x[1] < y[1];
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int l, r, x;
cin >> l >> r >> x;
seg[i] = {l, r, x};
}
for (int i = 0; i <= n; i++) {
p[i] = i;
}
sort(seg + 1, seg + 1 + m, cmp);
for (int i = 1; i <= m; i++) {
auto [l, r, w] = seg[i];
int cnt = w - (ask(r) - ask(l - 1));
int u = find(r);
for (int j = 1; j <= cnt; j++) {
updata(u, 1), ans[u] = 1;
p[u] = u - 1;
u = find(u - 1);
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " \n"[i == n];
}
return 0;
}

浙公网安备 33010602011771号