A - Cut (abc368 A)
題目大意
給定一個陣列,將右邊\(k\)個數保持原順序挪到左邊,輸出。
解題思路
即從左邊第\(n-k\)個數開始輸出即可。或者用rotate
函式轉換一下。
神奇的程式碼
#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, k;
cin >> n >> k;
k = n - k;
vector<int> a(n);
for (auto& i : a)
cin >> i;
rotate(a.begin(), a.begin() + k, a.end());
for (auto i : a)
cout << i << ' ';
cout << '\n';
return 0;
}
B - Decrease 2 max elements (abc368 B)
題目大意
給定一個陣列,每次將最大的兩個數減一,直到只有一個或無正數。輸出操作的次數。
解題思路
最多\(100\)個數,每個數最大 \(100\),因此最大的操作次數只有\(O(100^2)\),加上每次操作的複雜度是 \(O(n\log n)\),因此直接模擬的複雜度即為 \(O(n^3\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> a(n);
for (auto& i : a)
cin >> i;
int cnt = 0;
while (count_if(a.begin(), a.end(), [](int x) { return x > 0; }) > 1) {
sort(a.begin(), a.end(), greater<int>());
a[0]--;
a[1]--;
cnt++;
}
cout << cnt << '\n';
return 0;
}
C - Triple Attack (abc368 C)
題目大意
\(n\)個怪獸血量 \(h_i\),依次打它們。當前怪物死了則打下一個。
初始時刻\(t=0\),每時刻都會對當前怪物造成 \(1\)的傷害,如果 \(t \% 3 == 0\),則可以額外造成 \(2\)的傷害(即一共 \(3\)的傷害)。
問打死所有怪物時的時刻。
解題思路
直接模擬時刻流逝,其最大時刻高達\(O(10^{14})\),但注意到打怪物的時刻越多,傷害越高,因此對於每個怪獸,我們不必模擬時刻流逝,而是二分打死怪物的時刻 \(T\),看看時間\((t, T]\)的傷害是否足夠打死當前怪獸。
對每個怪獸都二分求出一下打死的時刻,最後的二分結果即為答案。
計算 \((t, T]\)時間的傷害,即為 \(T - t + \lfloor \frac{T}{3} \rfloor - \lfloor \frac{t}{3} \rfloor\)
神奇的程式碼
#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;
LL t = 0;
LL up = 1e18;
auto check = [](LL l, LL r) -> LL { return r - l + 2ll * (r / 3 - l / 3); };
auto solve = [&](LL t, int x) -> LL {
LL l = t, r = up;
while (l + 1 < r) {
LL mid = (l + r) / 2;
if (check(t, mid) < x)
l = mid;
else
r = mid;
}
return r;
};
while (n--) {
int x;
cin >> x;
t = solve(t, x);
}
cout << t << '\n';
return 0;
}
D - Minimum Steiner Tree (abc368 D)
題目大意
給定一棵樹,問保留\(k\)個點且連通的最小點數是多少。
解題思路
以第一個保留的點為根,進行\(DFS\)。
在 \(DFS\)過程中的當前點 \(u\),判斷其是否需要保留,則需要知道其子樹是否有需要保留的點,有則當前點 \(u\)需要保留。
因此 \(DFS\)返回的東西即為該子樹是否有需要保留的點
\(fixed\)。最後答案就是 fixed
點的數量。
神奇的程式碼
#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, k;
cin >> n >> k;
vector<vector<int>> edge(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--;
v--;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<int> fix(n, 0);
int root = 0;
for (int i = 0; i < k; ++i) {
cin >> root;
--root;
fix[root] = 1;
}
int ans = 0;
auto dfs = [&](auto dfs, int u, int fa) -> bool {
bool fixed = fix[u];
for (auto v : edge[u]) {
if (v == fa)
continue;
fixed |= dfs(dfs, v, u);
}
ans += fixed;
return fixed;
};
dfs(dfs, root, root);
cout << ans << '\n';
return 0;
}
E - Train Delay (abc368 E)
題目大意
給定\(n\)個城市和 \(m\)個火車的資訊,從\(s_i \to t_i\),時間是 \(S_i \to T_i\)。
先給定 \(x_1\),即第一個城市火車延誤出發時間,要求 \(x_2,x_3,...,x_n\),使得:
- 對於任意兩個火車資訊滿足 \(t_i = s_j, T_i \leq S_j\)的,也滿足\(T_i + x_i \leq S_j + x_j\),即原來可換乘的倆列車, 在延誤後仍可換乘。
- \(\sum_{i=2}^n x_i\)最小
解題思路
<++>
神奇的程式碼
F - Dividing Game (abc368 F)
題目大意
給定\(n\)個數, \(Anna\)杏菜和 \(Bruno\)玩遊戲, \(Anna\)先。
每輪,選一個數,將其變為真因子。
無法操作即輸。問最優情況下誰贏。
解題思路
知道博弈論的\(SG\)知識就可以解了。
每個數之間是互不干擾的,因此每個數可以視為一個獨立局面,因此整個局面的\(sg\)值就是每個獨立局面的 \(sg\)值的異或。
考慮一個獨立局面的 \(sg\)值怎麼求,即 \(sg[x]\)。根據 \(sg\)值定義,其為所有後繼情況的 \(sg\)值的 \(mex\),即 \(sg[x] = \mex_{x \% i == 0} (sg[i])\) 。
列舉因子數是\(O(\sqrt{x}) = 10^{2.5}\),而\(n=10^5\) ,因此花\(O(n\sqrt{x})\)預處理出所有數的\(sg\)值,然後異或一下,為 \(0\)則 先手輸,否則 先手贏。
神奇的程式碼
#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 = 1e5 + 8;
vector<int> sg(N);
sg[1] = 0;
auto getSG = [&](int x) {
set<int> hold{sg[1]};
for (int i = 2; i * i <= x; ++i) {
if (x % i == 0) {
hold.insert(sg[x / i]);
hold.insert(sg[i]);
}
}
for (int i = 0; i < N; ++i) {
if (hold.find(i) == hold.end()) {
return i;
}
}
assert(0);
};
for (int i = 2; i < N; ++i) {
sg[i] = getSG(i);
}
int n;
cin >> n;
int ans = 0;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
ans ^= sg[x];
}
if (ans) {
cout << "Anna" << '\n';
} else {
cout << "Bruno" << '\n';
}
return 0;
}
G - Add and Multiply Queries (abc368 G)
題目大意
給定兩個陣列\(a,b\),維護下列三種操作:
1 i x
,令\(a_i = x\)2 i x
,令\(b_i = x\)3 l r
,求最大值\(v\),其初始 \(v=0\),依次 \(i \in [l,r]\) ,令\(v = v + a_i\)或 \(v = v * b_i\)
題目保證操作三的答案不超過\(10^{18}\)。
解題思路
對於一次操作\(3\),很顯然對於 \(i \in [l,r]\) ,我令\(v = \max(v + a_i, v * b_i)\)。一次的複雜度是 \(O(n)\)。
注意到操作三的答案不超過\(10^{18}\),這意味著什麼呢。
首先,如果 \(b_i = 1\),我們很顯然是選擇操作 \(1\),而對於要判斷最大值的,必定有 \(b_i \geq 2\), \(v = \max(v + a_i, v*b_i) \geq 2v\),也就是說,每做一次判斷, \(v\)都會翻倍,最多翻 \(64\)次,就達到 \(10^{18}\)了。
所以,每次區間 \([l,r]\)中,實際上只有最多\(O(64)\)個 \(b_i \geq 2\),其餘都是 \(1\),這意味著都會選擇 \(v=v+a_i\)。
因此每次操作區間 \([l,r]\),我們直接模擬,
- 找到第一個 \(b_{pos} \geq 2\)
- 處理 \([l, pos-1]\)的操作,即 \(v = v + suma[l..pos-1]\)
- 處理 \([pos, pos]\)的操作,即 \(v = \max(v + a_{pos}, v * b_{pos})\)
- \(l = pos + 1\),重複第一個操作
最多跑 \(O(60)\)次就跑完了,因為帶修改,第一步操作就用線段樹上二分,透過區間最大值>1
找到其位置,第二步操作就是一個區間和。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 2e5 + 8;
class info {
public:
LL sum;
int maxx;
info(LL sum = 0, int maxx = 0) : sum(sum), maxx(maxx) {}
info operator+(const info& rhs) const {
return info(sum + rhs.sum, max(maxx, rhs.maxx));
}
};
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
info val[N << 2];
void build(int root, int l, int r, vector<int>& a, vector<int>& b) {
if (l == r) {
val[root] = info(a[l], b[l]);
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid, a, b);
build(rson, mid + 1, r, a, b);
val[root] = val[lson] + val[rson];
}
void update(int root, int l, int r, int pos, info& v) {
if (l == r) {
val[root] = v;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
update(lson, l, mid, pos, v);
else
update(rson, mid + 1, r, pos, v);
val[root] = val[lson] + val[rson];
}
info query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return val[root];
}
int mid = (l + r) >> 1;
info resl{}, resr{};
if (L <= mid)
resl = query(lson, l, mid, L, R);
if (R > mid)
resr = query(rson, mid + 1, r, L, R);
return resl + resr;
}
int findfirst(int root, int l, int r, int L, int R) {
if (l > R || r < L) {
return 0;
}
if (L <= l && r <= R && val[root].maxx < 2) {
return 0;
}
if (l == r) {
return l;
}
int mid = (l + r) >> 1;
int ret = findfirst(lson, l, mid, L, R);
if (ret == 0) {
ret = findfirst(rson, mid + 1, r, L, R);
}
return ret;
}
} seg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; ++i)
cin >> a[i];
vector<int> b(n + 1);
for (int i = 1; i <= n; ++i)
cin >> b[i];
seg.build(1, 1, n, a, b);
int q;
cin >> q;
while (q--) {
int op;
cin >> op;
if (op == 1) {
int pos, x;
cin >> pos >> x;
a[pos] = x;
info v(a[pos], b[pos]);
seg.update(1, 1, n, pos, v);
} else if (op == 2) {
int pos, x;
cin >> pos >> x;
b[pos] = x;
info v(a[pos], b[pos]);
seg.update(1, 1, n, pos, v);
} else {
int l, r;
cin >> l >> r;
LL ans = a[l];
l++;
while (l <= r) {
int pos = seg.findfirst(1, 1, n, l, r);
if (pos == 0) {
ans += seg.query(1, 1, n, l, r).sum;
break;
} else {
info v = seg.query(1, 1, n, l, pos - 1);
ans += v.sum;
ans = max(ans + a[pos], ans * b[pos]);
l = pos + 1;
}
}
cout << ans << '\n';
}
}
return 0;
}