2024-09-14 22:53阅读: 678评论: 0推荐: 3

AtCoder Beginner Contest 371

A - Jiro (abc371 A)

题目大意

三个人,给定他们相互之间的大小关系。

问谁在中间。

解题思路

排个序,大小结果就是题目给的,因为问的是中间是谁,所以写的时候并没在意是升序排还是降序排。

神奇的代码
#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 a, b, c;
cin >> a >> b >> c;
array<int, 3> id{};
iota(id.begin(), id.end(), 0);
map<pair<int, int>, int> mp;
mp[{0, 1}] = a[0] == '>';
mp[{1, 0}] = a[0] == '<';
mp[{0, 2}] = b[0] == '>';
mp[{2, 0}] = b[0] == '<';
mp[{1, 2}] = c[0] == '>';
mp[{2, 1}] = c[0] == '<';
sort(id.begin(), id.end(), [&](int x, int y) { return mp[{x, y}]; });
string ans = "ABC";
cout << ans[id[1]] << '\n';
return 0;
}


B - Taro (abc371 B)

题目大意

n个家庭,依次出生 m个孩子,每个家庭的第一个出生的男婴儿会授予名字 taro

依次回答每个孩子的名字是不是taro

解题思路

维护每个家庭是否有男婴儿出生,然后依次判断每个孩子是不是男的,且是第一个男的即可。

神奇的代码
#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<int> tora(n, 0);
while (m--) {
int f;
string s;
cin >> f >> s;
--f;
if (s[0] == 'F' || tora[f]) {
cout << "No" << '\n';
} else {
tora[f] = 1;
cout << "Yes" << '\n';
}
}
return 0;
}


C - Make Isomorphic (abc371 C)

题目大意

给定两张无向图G,H

给定一个操作代价,在图H中给(i,j)连一条边或删一条边的花费 aij

问最小的代价,使得两张图同构。

解题思路

同构即存在一种点标号的映射关系,使得映射后两张图一模一样(包括点标号和对应的边)。

由于点数只有8,我们先花 O(8!)枚举这个映射(即一个排列),然后看这个映射关系下,使得两张图相等所需要的代价。

计算代价即花O(n2)的时间,枚举所有的边(i,j),i<j,如果 G存在该边但H不存在或G不存在但 H存在,进行一次操作。 所有的操作代价累加即为该映射关系的操作代价。

所有的映射关系的代价的最小值即为答案。

时间复杂度为O(n!n2)

神奇的代码
#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;
cin >> n;
array<int, 2> m{};
array<vector<vector<int>>, 2> edge{vector<vector<int>>(n, vector<int>(n)),
vector<vector<int>>(n, vector<int>(n))};
for (int k = 0; k < 2; ++k) {
cin >> m[k];
for (int i = 0; i < m[k]; ++i) {
int u, v;
cin >> u >> v;
--u, --v;
edge[k][u][v] = edge[k][v][u] = 1;
}
}
vector<vector<int>> a(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j) {
cin >> a[i][j];
a[j][i] = a[i][j];
}
vector<int> id(n);
iota(id.begin(), id.end(), 0);
int ans = 1e9 + 7;
auto solve = [&](vector<int> id) {
int res = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (edge[0][id[i]][id[j]] != edge[1][i][j]) {
res += a[i][j];
}
}
}
return res;
};
do {
ans = min(ans, solve(id));
} while (next_permutation(id.begin(), id.end()));
cout << ans << '\n';
return 0;
}


D - 1D Country (abc371 D)

题目大意

一维数轴,给定n个村落的位置和人口。

q个询问,每个询问给定 l,r,问位于l,r之间的村落人口数量。

解题思路

题意给的村落位置本身有序。

直接维护关于村落的人口前缀和。

对于每个询问,二分找到位于l,r区间的村落的左右端点,然后通过前缀和求得这期间的人口数量。

时间复杂度是O(qlogn)

神奇的代码
#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;
cin >> n;
vector<int> x(n);
for (auto& i : x)
cin >> i;
vector<LL> p(n);
for (auto& i : p)
cin >> i;
vector<LL> presum(n);
partial_sum(p.begin(), p.end(), presum.begin());
auto get_sum = [&](int l, int r) {
if (l > r)
return 0ll;
return presum[r] - (l ? presum[l - 1] : 0);
};
int q;
cin >> q;
while (q--) {
int l, r;
cin >> l >> r;
auto L = lower_bound(x.begin(), x.end(), l) - x.begin();
auto R = upper_bound(x.begin(), x.end(), r) - x.begin();
cout << get_sum(L, R - 1) << '\n';
}
return 0;
}


E - I Hate Sigma Problems (abc371 E)

题目大意

给定n个数 a,定义 f(l,r)表示区间 [l,r]的数的种类。

i=1nj=inf(i,j)

解题思路

考虑枚举j的话会发现不太可行,考虑直接的一个排列 1,2,3,4,5...,对于每个 j,每个i都对应了一个不同的f(i,j) ,感觉无法合并。

考虑答案的来源,即贡献的来源,是每一个数。比如2,3,2,5f(2,3)=2=1+13,2分别对答案有 1的贡献。而 f(1,3)=2=1+1,这里同样是 3,2对答案有 1的贡献,但这里有两个 2,只有其中的一个才有 1的贡献,我们可以规定最右边的 21的贡献。

我们的视角从 求f(l,r)的值 变成了求 每一个数 ai 在多少个区间 (l,r)1的贡献。

很显然,这个l的取值是 [1,l], 而r的取值要满足 [i,r]没有另一个 ai,即计 nxti表示下一个 ai的位置,那么 r的取值就是 [i,nxti)。因此对于一个ai,它有贡献的区间数量即为 i×(nxtii) 。所有的ai累加即为答案。

nxti的求法就倒序扫一遍,维护 lasti表示上一个数 i出现的位置即可。

即答案就是 i=1ni(nxtii),时间复杂度是O(n)

神奇的代码
#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;
cin >> n;
vector<int> a(n);
for (auto& i : a) {
cin >> i;
--i;
}
vector<int> r(n);
vector<int> pos(n, n);
for (int i = n - 1; i >= 0; --i) {
r[i] = pos[a[i]];
pos[a[i]] = i;
}
fill(pos.begin(), pos.end(), 0);
LL ans = 0;
for (int i = 0; i < n; ++i) {
int lcnt = i + 1;
int rcnt = r[i] - i;
ans += 1ll * lcnt * rcnt;
}
cout << ans << '\n';
return 0;
}


F - Takahashi in Narrow Road (abc371 F)

题目大意

一维数轴,n个人,依次完成以下 q个目标。

对于第 i个目标,让第 ti个人移动到 qi位置。

每次操作,可以让一个人向左右移动一格,如果目标位置有人则不能移动,得让对方先移动。

求完成所有目标所需要的最小操作次数。

解题思路

直接模拟整个过程即可。

考虑一个人的移动,移动过程中会推着其他人一起移动,我们就将这些人合并成一个块一起移动。

而这个人一开始移动时,它可能会从块里分离出来,那我们就从这个块里分离出来,然后移动。

每次移动当然不是一格一格移动,而是直接移动到终点,或者中间碰到了别人,合并成一个新的块。

这样做,考虑其复杂度,每次操作只会新增一个块,然后会合并若干个块。整个过程只会产生O(q)个块,每个块只会合并一次,单次移动的复杂度是 O(logn)的话,整个模拟过程的时间复杂度就是 O((n+q)logn)

如何模拟一堆堆的块呢?其实就是一棵珂朵莉树,其实就是一个set维护 每个块的信息,能分离一个块,移动块时找到下一个块的位置,判断能否合并即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
struct Block {
int l, r, id; // pos -> [l,r] people -> [id, id + r - l + 1]
bool operator<(const Block& b) const { return id < b.id; }
inline int cnt() const { return r - l + 1; }
inline int lid() const { return id; }
inline int rid() const { return id + cnt() - 1; }
bool intersect(const Block& b, int is_right) const {
if (is_right)
return r >= b.l;
else
return l <= b.r;
}
pair<Block, Block> cut(int t) const {
int cnt = t - id;
return {{l, l + cnt - 1, id}, {l + cnt, r, id + cnt}};
}
Block shift(int dis) const {
auto ret = *this;
ret.l += dis;
ret.r += dis;
return ret;
}
Block merge(const Block& b) const {
assert(r + 1 == b.l || b.r + 1 == l);
return {min(l, b.l), max(r, b.r), min(id, b.id)};
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
set<Block> p;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
p.insert({x, x, i});
}
auto get_block = [&](int t) {
auto it = prev(p.upper_bound({0, 0, t}));
return it;
};
auto get_pos = [&](int t) {
auto it = get_block(t);
int ret = it->l + t - it->id;
return ret;
};
auto shift_block = [&](int t, int g) {
auto it = *get_block(t);
auto dis = g - get_pos(t);
it.l += dis;
it.r += dis;
return it;
};
auto solve = [&](int t, int g) {
LL sum = 0;
while (true) {
int pos = get_pos(t);
if (pos == g)
break;
auto it = get_block(t);
if (pos < g) {
if (it->lid() != t) {
auto [lblock, rblock] = it->cut(t);
p.erase(it);
p.insert(lblock);
p.insert(rblock);
} else {
auto sit = shift_block(t, g);
auto nit = next(it);
if (nit == p.end() || !sit.intersect(*nit, true)) {
sum += 1ll * it->cnt() * (g - pos);
p.erase(it);
p.insert(sit);
} else {
auto shift = nit->l - it->r - 1;
sum += 1ll * sit.cnt() * shift;
sit = it->shift(shift);
auto nblock = sit.merge(*nit);
auto tmp = *nit;
p.erase(it);
p.erase(tmp);
p.insert(nblock);
}
}
} else {
if (it->rid() != t) {
auto [lblock, rblock] = it->cut(t + 1);
p.erase(it);
p.insert(lblock);
p.insert(rblock);
} else {
auto sit = shift_block(t, g);
if (it == p.begin() || !sit.intersect(*prev(it), false)) {
sum += 1ll * sit.cnt() * (pos - g);
p.erase(it);
p.insert(sit);
} else {
auto pit = prev(it);
auto shift = it->l - pit->r - 1;
sum += 1ll * sit.cnt() * shift;
sit = it->shift(-shift);
auto nblock = sit.merge(*pit);
auto tmp = *pit;
p.erase(it);
p.erase(tmp);
p.insert(nblock);
}
}
}
}
return sum;
};
int q;
cin >> q;
LL ans = 0;
while (q--) {
int t, g;
cin >> t >> g;
--t;
LL ret = solve(t, g);
ans += ret;
}
cout << ans << '\n';
return 0;
}


G - Lexicographically Smallest Permutation (abc371 G)

题目大意

给定两个排列p,a

可进行一种操作任意次,即令 ai=api

问得到的 a的字典序的最小值。

解题思路

替换操作可以看成是有若干个环的图,有连边ipi

问题就是求一个 x,使得每个点往前走 x步,得到的新的数组的字典序最小。

由字典序的比较顺序,首先是让第一个数最小。

那就找第一个数所在的环,遍历一遍,找到数最小的位置,得到一个偏移量 b。记该环的大小为a

这样,只要我们最后的偏移量 x满足 xb(moda),这样第一个位置就是最小的情况。

然后考虑下一个数所在的环(如果和1在同一个环,就忽略,继续找下一个不在之前考虑的环),在该环中,我们同样要找最小的值,但和之前直接遍历环的每个元素不同,由于之前有个限制xb(moda),因此在该环里,我们只能看第x个点,第 x+a个点,第 x+2a个点,...,这哪个点权最小(要保持不破坏之前考虑的环的偏移量)。

假设在有限个点,我们找到数最小的位置 B,即该环的大小为 A,那就是说,我们最终的偏移量 x要满足两个同余等式: xb(moda)xB(modA),通过扩展中国剩余定理可以将其合并成一个等价的同余式xb0(moda0)

然后就继续遍历剩下的环,不断合并同余式,最后偏移量就是b0%a0

官方题解a,b的值会超22367,因此下面的c++代码尽管开了 __in128仍会爆。

c++代码
#include <bits/stdc++.h>
using namespace std;
using LL = __int128;
LL x, y, d;
void exgcd(LL& x, LL& y, LL a, LL b) {
if (!b)
d = a, x = 1, y = 0;
else
exgcd(y, x, b, a % b), y -= a / b * x;
}
LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; }
LL lcm(LL a, LL b) { return a / gcd(a, b) * b; }
LL a, b, A, B;
void merge() {
exgcd(x, y, a, A);
LL c = B - b;
assert(c % d == 0);
x = x * c / d % (A / d);
if (x < 0)
x += A / d;
LL mod = lcm(a, A);
b = (a * x + b) % mod;
if (b < 0)
b += mod;
a = mod;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> p(n), v(n);
for (auto& i : p) {
cin >> i;
--i;
}
for (auto& i : v)
cin >> i;
vector<vector<int>> cir;
vector<int> vis(n);
for (int i = 0; i < n; ++i) {
if (vis[i])
continue;
vector<int> h{i};
for (int j = p[i]; j != i; j = p[j]) {
vis[j] = 1;
h.push_back(j);
}
cir.push_back(h);
}
a = 1;
b = 0;
for (int i = 0; i < cir.size(); ++i) {
auto& h = cir[i];
int id = b % h.size();
vector<int> used(h.size(), 0);
for (int i = id;; i = (i + a) % h.size()) {
if (used[i])
break;
used[i] = 1;
if (v[h[i]] < v[h[id]])
id = i;
}
A = h.size();
B = id;
if (i > 0)
merge();
else
a = A, b = B;
}
LL step = b % a;
vector<int> ans(n);
for (auto& h : cir) {
for (int i = 0, j = step % h.size(); i < h.size();
i++, j = (j + 1) % h.size()) {
ans[h[i]] = v[h[j]];
}
}
for (int i = 0; i < n; ++i) {
cout << ans[i] << " \n"[i == n - 1];
}
return 0;
}

怎么办呢,用chatgpt改成pythonb吧jls的代码貌似没用高精,研究研究

python代码
# 扩展欧几里得算法
def exgcd(a, b):
if b == 0:
return a, 1, 0
d, x1, y1 = exgcd(b, a % b)
x = y1
y = x1 - (a // b) * y1
return d, x, y
# 求最大公约数
def gcd(a, b):
return a if b == 0 else gcd(b, a % b)
# 求最小公倍数
def lcm(a, b):
return a // gcd(a, b) * b
# 初始化全局变量
x, y, d = 0, 0, 0
a, b, A, B = 0, 0, 0, 0
# 合并两个同余方程
def merge():
global a, b, A, B, x, y, d
d, x, y = exgcd(a, A)
c = B - b
assert c % d == 0
x = (x * (c // d)) % (A // d)
if x < 0:
x += A // d
mod = lcm(a, A)
b = (a * x + b) % mod
if b < 0:
b += mod
a = mod
def main():
n = int(input())
p = [int(i) - 1 for i in input().split()]
v = list(map(int, input().split()))
# 寻找环
cir = []
vis = [0] * n
for i in range(n):
if vis[i]:
continue
h = [i]
vis[i] = 1
j = p[i]
while j != i:
vis[j] = 1
h.append(j)
j = p[j]
cir.append(h)
global a, b, A, B
a, b = 1, 0
for i, h in enumerate(cir):
id = b % len(h)
used = [0] * len(h)
j = id
while not used[j]:
used[j] = 1
if v[h[j]] < v[h[id]]:
id = j
j = (j + a) % len(h)
A = len(h)
B = id
if i > 0:
merge()
else:
a, b = A, B
step = b % a
ans = [0] * n
for h in cir:
for i in range(len(h)):
j = (step + i) % len(h)
ans[h[i]] = v[h[j]]
print(" ".join(map(str, ans)))
if __name__ == "__main__":
main()


本文作者:~Lanly~

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

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

posted @   ~Lanly~  阅读(678)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.