2024-10-02 20:20阅读: 263评论: 0推荐: 2

AtCoder Beginner Contest 373

省流版
  • A. 暴力即可
  • B. 求出字母位置,绝对值相加即可
  • C. 显然答案为两个数组的最大值的和
  • D. 注意直接BFS的点权范围不超过题目范围,直接BFS即可
  • E. 发现单调性,二分票数,用前缀和O(1)判断可行性即可
  • F. 朴素背包DP,相同重量的物品一起考虑,用优先队列求解l个相同重量物品最大价值的最优取法即可

A - September (abc373 A)

题目大意

给定12个字符串,问第 i个字符串的长度是不是 i

解题思路

按照题意依次判断即可,python可以一行。

神奇的代码
print(sum(i + 1 == len(input().strip()) for i in range(12)))


B - 1D Keyboard (abc373 B)

题目大意

给定一个键盘,从左到右的26个字母分别是什么。

现在依次输入abcdefgh...xyz,初始手指在a处,然后要移动到b处按b,然后移动到c处按c...

问输入完这26个字符需要移动的距离数。

解题思路

当前手指在位置x,移动到下一个字母的位置 y ,距离数是|xy|,求出 pos[i]表示字符 i的位置,答案就是 |pos[i]pos[i1]|的累加。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s;
cin >> s;
array<int, 26> pos{};
for (int i = 0; i < 26; ++i)
pos[s[i] - 'A'] = i;
int ans = 0;
for (int i = 1; i < 26; ++i)
ans += abs(pos[i] - pos[i - 1]);
cout << ans << '\n';
return 0;
}


C - Max Ai+Bj (abc373 C)

题目大意

给定两个数组a,b,分别从中选一个数,使得和最大。

解题思路

显然选的两个数分别为最大的数即可。python可以两行。

神奇的代码
input()
print(max(map(int, input().split())) + max(map(int, input().split())))


D - Hidden Weights (abc373 D)

题目大意

给定一张有向图,边有边权。

确定点权xi,满足对于每一条有向边 uivi,wi,满足 xvixui=wi

题目保证有解。

解题思路

视为无向图,但反过来的边权是wi,然后从未访问过的点开始BFS,其点权为0,然后按照上述等式得到周围点的点权即可。

点数n2×105,边权 109,所以点权不会超过2×1014,满足题意范围的 1018

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<array<int, 2>>> edge(n);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
--u, --v;
edge[u].push_back({v, w});
edge[v].push_back({u, -w});
}
vector<LL> ans(n);
queue<int> team;
vector<int> vis(n);
auto BFS = [&](int st) {
team.push(st);
vis[st] = 1;
while (!team.empty()) {
int u = team.front();
team.pop();
for (auto& [v, w] : edge[u]) {
if (vis[v])
continue;
ans[v] = ans[u] + w;
vis[v] = 1;
team.push(v);
}
}
};
for (int i = 0; i < n; ++i)
if (!vis[i])
BFS(i);
for (int i = 0; i < n; ++i)
cout << ans[i] << " \n"[i == n - 1];
return 0;
}


E - How to Win the Election (abc373 E)

题目大意

n个候选人, k张票。最终票数最多的m个候选人获胜,同票数的都会获胜,因此可能获胜的可能会超过 m个 。

现已知部分投票情况。 问每一个候选人,需要给他至少多少张票,才能使得他一定会获胜,即无论剩余票数如何分配,他始终都是票数前m多的。

解题思路

对于当前第i个候选人,他的票数vi目前排第 ranki名,那他至少还要多少票才能稳赢呢?

最朴素的想法就是,枚举这个票数,即假设他得到了x票,看看能否稳赢? (如果你没想到二分,可能是因为这个最朴素的做法没想到——通过增设条件来解决问题,从这个条件其实是很容易看出来具有单调性,进而可以二分)

那能否稳赢呢?假设他得到了x票,那此时的票数是 vi+x,通过二分可以得到该票数的排名,假设是ri名。然后还剩下left张票没投。

  • 如果 ri>m,那他必不可能赢了。
  • 如果 rim,即此时他前面只有 ri1个人,剩下的票分配给其他人,只要少于 danger=mri+1个人的票数超过他,那他就能赢。

考虑最坏情况,即为票数vi+x的最大的danger个人的票数达到vi+x+1所需要的票数,少于剩余未投的票数 left,那第 i个人一定是票数前 m 大的,即稳赢。而前者的票数计算相当于是danger×(vi+x+1)vj ,事先对v排序,后者其实就是一个区间和,可以用前缀和优化,在 O(1)的时间内得到。

到此,我们可以 O(1)判断当前枚举的票数 x是不是稳赢的。但枚举的票数的范围有 O(k),但容易发现该票数是否稳赢之间具有单调性——给越多票,稳赢的可能性越高。

因此我们不必一个一个枚举票数,而是二分该票数,然后 通过前缀和,O(1) 判断是否可行即可。

对所有人都这么求一遍,总的时间复杂度就是O(nlogk)

上述验证的想法其实很朴素,考虑最坏情况,就看票数小于他的那么些人能否赶超。所需票数的计算可以用前缀和优化。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
LL k;
cin >> n >> m >> k;
vector<LL> a(n);
for (auto& i : a)
cin >> i;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return a[i] > a[j]; });
vector<LL> sum(n);
sum[0] = a[id[0]];
for (int i = 1; i < n; ++i)
sum[i] = sum[i - 1] + a[id[i]];
vector<LL> ans(n);
LL left = k - accumulate(a.begin(), a.end(), 0ll);
auto get_sum = [&](int l, int r, int ex) {
if (l > r)
return 0ll;
LL s = 0;
if (l <= ex && ex <= r) {
r += 1;
s -= a[id[ex]];
}
s += sum[r] - (l ? sum[l - 1] : 0);
return s;
};
auto solve = [&](int pos, int rank) {
auto check = [&](LL votes) {
LL now_votes = a[pos] + votes;
int now_rank = lower_bound(id.begin(), id.end(), now_votes,
[&](int x, LL v) { return a[x] > v; }) -
id.begin();
int left_candi = m - now_rank;
LL demand = left_candi * (now_votes + 1) -
get_sum(now_rank, now_rank + left_candi - 1, rank);
LL ano = left - votes;
return ano < demand;
};
LL l = -1, r = left;
while (l + 1 < r) { //(l, r]
LL mid = (l + r) >> 1;
if (check(mid))
r = mid;
else
l = mid;
}
return check(r) ? r : -1;
};
for (int i = 0; i < n; ++i) {
ans[id[i]] = solve(id[i], i);
}
for (int i = 0; i < n; ++i)
cout << ans[i] << " \n"[i == n - 1];
return 0;
}


F - Knapsack with Diminishing Values (abc373 F)

题目大意

n种物品,重量 wi,价值vi,无限个。

背包容量 W,现在选物品放入背包,不超背包容量,价值最大。

当第 i种物品放 k个时,其价值为 kvik2

解题思路

考虑最朴素的背包做法,即dp[i][j]表示考虑前 i个物品,放的容量是 j的最大价值。转移即考虑当前物品放多少个,其时间复杂度为 O(nw2)。此处 n3000,无法通过。

究其复杂度,主要在考虑每种物品,其重量是wi,我们需要考虑该物品的数量为 1,2,...,Wwi 。如果每个物品的重量都挺小的,那么物品数量的数量级就是O(W)。转移的复杂度就是 O(W)

怎么办呢?对于重量都小的,我们能否一起考虑呢?

即我们不依次考虑每种物品,而是依次考虑重量为i的所有物品,即设 dp[i][j]表示考虑重量i的物品,放的容量是 j的最大价值。这样考虑的物品数量就是O(Wi),对所有的i的转移复杂度累加,其复杂度就是 O(w2logw)

复杂度对了,考虑如何转移呢,即dp[i][j],然后枚举选重量为 i的物品的数量个数 l,假设其物品的价值分别是 v1,v2,v3,...,现在要取 l个,怎么取最优?

由于每种物品的价值,随着取的个数的增加,其价值会减少,最朴素的想法就是一个一个取。

考虑每种物品是一行,从左到右的物品的价值依次减少,即设fi(k)=kvik2,第一行第一个物品的价值是f1(1),第二个物品的价值就是f1(2)f1(1),第三个物品的价值就是 f1(3)f1(2),而第二行的第一个物品的价值就是f2(1)

上述怎么取的问题就变成,每行取一个前缀的物品,一共 l个,价值最大。

我们就考虑每行的第一个未取的物品,每次就取这些行里,价值最大的。如何快速找到这些的最大值,用优先队列维护即可,队列里的元素最多也就O(n)

由于取物品数量l是从 1开始枚举的,因此求最优的取法就这样依次迭代,从优先队列里取价值最大的物品即可。

总的时间复杂度是 O(w2logwlogn)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e18;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, w;
cin >> n >> w;
vector<vector<int>> a(w + 1);
for (int i = 0; i < n; i++) {
int w, v;
cin >> w >> v;
a[w].push_back(v);
}
vector<LL> dp(w + 1, -inf);
dp[0] = 0;
auto f = [&](int k, int v) { return 1ll * k * v - k * k; };
auto get_val = [&](int k, int v) { return f(k, v) - f(k - 1, v); };
for (int i = 0; i <= w; i++) {
if (!a[i].empty()) {
vector<LL> dp2 = dp;
for (int j = 0; j <= w; j++) {
priority_queue<pair<LL, pair<int, int>>> q;
for (auto x : a[i]) {
q.push({get_val(1, x), {1, x}});
}
LL sum = 0;
for (int l = i; j + l <= w; l += i) {
auto [val, p] = q.top();
auto [k, v] = p;
q.pop();
sum += val;
dp2[j + l] = max(dp2[j + l], dp[j] + sum);
q.push({get_val(k + 1, v), {k + 1, v}});
}
}
dp.swap(dp2);
}
}
LL ans = *max_element(dp.begin(), dp.end());
cout << ans << '\n';
return 0;
}


G - No Cross Matching (abc373 G)

题目大意

二维平面,给定两组点p,qn个,打乱 q的顺序,使得n个线段 piqi互不相交。

给定一种 q的顺序或告知不可能。

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18445049

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(263)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.