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)\)連一條邊或刪一條邊的花費 \(a_{ij}\)。
問最小的代價,使得兩張圖同構。
解題思路
同構即存在一種點標號的對映關係,使得對映後兩張圖一模一樣(包括點標號和對應的邊)。
由於點數只有\(8\),我們先花 \(O(8!)\)列舉這個對映(即一個排列),然後看這個對映關係下,使得兩張圖相等所需要的代價。
計算代價即花\(O(n^2)\)的時間,列舉所有的邊\((i,j), i < j\),如果 \(G\)存在該邊但\(H\)不存在或\(G\)不存在但 \(H\)存在,進行一次操作。 所有的操作代價累加即為該對映關係的操作代價。
所有的對映關係的代價的最小值即為答案。
時間複雜度為\(O(n!n^2)\)。
神奇的程式碼
#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(q\log 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> 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]\)的數的種類。
求 \(\sum_{i=1}^{n}\sum_{j=i}^{n} f(i,j)\)
解題思路
考慮列舉\(j\)的話會發現不太可行,考慮直接的一個排列 \(1,2,3,4,5...\),對於每個 \(j\),每個\(i\)都對應了一個不同的\(f(i,j)\) ,感覺無法合併。
考慮答案的來源,即貢獻的來源,是每一個數。比如\(2,3,2,5\), \(f(2,3) = 2 = 1 + 1\), \(3,2\)分別對答案有 \(1\)的貢獻。而 \(f(1,3) = 2 = 1 + 1\),這裡同樣是 \(3,2\)對答案有 \(1\)的貢獻,但這裡有兩個 \(2\),只有其中的一個才有 \(1\)的貢獻,我們可以規定最右邊的 \(2\)有 \(1\)的貢獻。
我們的視角從 求\(f(l,r)\)的值 變成了求 每一個數 \(a_i\) 在多少個區間 \((l,r)\)有 \(1\)的貢獻。
很顯然,這個\(l\)的取值是 \([1,l]\), 而\(r\)的取值要滿足 \([i,r]\)沒有另一個 \(a_i\),即計 \(nxt_i\)表示下一個 \(a_i\)的位置,那麼 \(r\)的取值就是 \([i,nxt_i)\)。因此對於一個\(a_i\),它有貢獻的區間數量即為 \(i \times (nxt_i - i)\) 。所有的\(a_i\)累加即為答案。
而 \(nxt_i\)的求法就倒序掃一遍,維護 \(last_i\)表示上一個數 \(i\)出現的位置即可。
即答案就是 \(\sum_{i=1}^{n} i(nxt_i - i)\),時間複雜度是\(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\)個目標,讓第 \(t_i\)個人移動到 \(q_i\)位置。
每次操作,可以讓一個人向左右移動一格,如果目標位置有人則不能移動,得讓對方先移動。
求完成所有目標所需要的最小操作次數。
解題思路
研究G題了這題還沒看好像就是一棵珂朵莉樹
神奇的程式碼
G - Lexicographically Smallest Permutation (abc371 G)
題目大意
給定兩個排列\(p,a\)。
可進行一種操作任意次,即令 \(a_i = a_{p_i}\)。
問得到的 \(a\)的字典序的最小值。
解題思路
替換操作可以看成是有若干個環的圖,有連邊\(i \to p_i\)。
問題就是求一個 \(x\),使得每個點往前走 \(x\)步,得到的新的陣列的字典序最小。
由字典序的比較順序,首先是讓第一個數最小。
那就找第一個數所在的環,遍歷一遍,找到數最小的位置,得到一個偏移量 \(b\)。記該環的大小為\(a\)。
這樣,只要我們最後的偏移量 \(x\)滿足 \(x \equiv b (\mod a)\),這樣第一個位置就是最小的情況。
然後考慮下一個數所在的環(如果和\(1\)在同一個環,就忽略,繼續找下一個不在之前考慮的環),在該環中,我們同樣要找最小的值,但和之前直接遍歷環的每個元素不同,由於之前有個限制\(x \equiv b (\mod a)\),因此在該環裡,我們只能看第\(x\)個點,第 \(x + a\)個點,第 \(x + 2a\)個點,...,這哪個點權最小(要保持不破壞之前考慮的環的偏移量)。
假設在有限個點,我們找到數最小的位置 \(B\),即該環的大小為 \(A\),那就是說,我們最終的偏移量 \(x\)要滿足兩個同餘等式: \(x \equiv b (\mod a)\)和\(x \equiv B (\mod A)\),透過擴充套件中國剩餘定理可以將其合併成一個等價的同餘式\(x \equiv b_0 (\mod a_0)\)。
然後就繼續遍歷剩下的環,不斷合併同餘式,最後偏移量就是\(b_0 \% a_0\)。
據官方題解,\(a,b\)的值會超\(2^{2367}\),因此下面的\(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()