A - A Healthy Breakfast (abc360 A)
題目大意
給定一個字串包含RMS
,問R
是否在S
的左邊。
解題思路
比較R
和S
的下標,誰小即誰在左邊。
神奇的程式碼
#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;
cout << (s.find('R') < s.find('M') ? "Yes" : "No") << '\n';
return 0;
}
B - Vertical Reading (abc360 B)
題目大意
給定兩個字串\(s,t\),找到一個 \(1 \leq c \leq w < |s|\),使得將 \(s\)拆開,每個子串有\(w\)個字母, 對每個子串取第\(c\)位字母出來(不存在則不取),使其組成 \(t\)。
解題思路
由於\(|s| \leq 100\),直接花 \(O(|s|^2)\)列舉 \(c,w\),然後拆開子串、拼接、比較即可。時間複雜度是 \(O(n^3)\)。
神奇的程式碼
#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, t;
cin >> s >> t;
bool ok = false;
for (int i = 1; i < s.size(); i++) {
vector<string> sub;
for (int j = 0; j < s.size(); j += i) {
sub.push_back(s.substr(j, i));
}
for (int j = 0; j < i; ++j) {
string ans;
for (auto& x : sub) {
if (j < x.size())
ans += x[j];
}
if (ans == t) {
ok = true;
}
}
}
if (ok)
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}
C - Move It (abc360 C)
題目大意
給定 \(n\)個箱子。再給定\(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<vector<int>> box(n);
vector<int> pos(n);
for (auto& i : pos)
cin >> i;
for (int i = 0; i < n; ++i) {
int w;
cin >> w;
box[pos[i] - 1].push_back(w);
}
int ans = 0;
for (auto& i : box) {
if (i.empty())
continue;
sort(i.begin(), i.end(), greater<int>());
ans += accumulate(i.begin(), i.end(), 0) - i[0];
}
cout << ans << '\n';
return 0;
}
D - Ghost Ants (abc360 D)
題目大意
一維數軸,螞蟻移動,有方向,速度一樣。碰撞時不改變方向,繼續保持原方向行走。
經過\(t\)時刻後,問碰撞的螞蟻對數。
解題思路
同方向的螞蟻不會碰撞,因此僅考慮不同向的。
考慮往右的螞蟻會與哪些往左的螞蟻碰撞。
由於所有螞蟻速度一樣,因此由相向運動得知,假設當前往右的螞蟻位置為\(x\),那麼所有往左的位於\([x, x + 2t]\)的螞蟻都會與其相撞。
根據螞蟻前進方向對座標排序,二分一下就能找到往左的位於 \([x, x + 2t]\)的螞蟻數量。
所有數量累加即為答案。時間複雜度是 \(O(n \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, t;
string dir;
cin >> n >> t >> dir;
array<vector<int>, 2> pos;
for (int i = 0; i < n; ++i) {
int p;
cin >> p;
pos[dir[i] - '0'].push_back(p);
}
for (auto& x : pos)
sort(x.begin(), x.end());
LL ans = 0;
for (int i = 1; i < 2; ++i) {
auto& cur = pos[i];
auto& nxt = pos[i ^ 1];
for (auto x : cur) {
LL l = x;
LL r = x + t * (i == 1 ? 1 : -1) * 2ll;
if (l > r)
swap(l, r);
auto ll = lower_bound(nxt.begin(), nxt.end(), l);
auto rr = upper_bound(nxt.begin(), nxt.end(), r);
ans += rr - ll;
}
}
cout << ans << '\n';
return 0;
}
E - Random Swaps of Balls (abc360 E)
題目大意
\(n\)個球,其中第一個是黑球,其餘是白球。
進行以下操作 \(k\)次:
- 隨機兩次獨立選取 \(i,j\),交換第 \(i\)個球和第 \(j\)個球。
問黑球位置的期望值。
解題思路
假設最終黑球位於第\(i\)個球的機率是\(p_i\),根據期望定義,答案就是 \(\sum_{i=1}^{n} i p_i\)。考慮如何求\(p_i\)。
容易發現第 \(2,3,...,n\)個球沒有本質區別,其機率都是一樣的,因此最終我們需要求的就兩個數
- \(p_1\)即位於第一個球的機率
- \(p_2\)即位於非第一個球的機率
容易發現經過第\(k\)次操作的機率,僅依賴於第 \(k-1\)次的情況,因此可以迭代球,即設 \(dp[k][0/1]\)表示經過 \(k\)次操作後,球位於第一個球/不位於第一個球的機率。
轉移就考慮本次操作是否將球移動到第一個球或非第一個球。設移動機率\(move = 2\frac{1}{n}\frac{n-1}{n}\),不移動機率\(stay = 1 - move\),移動到某一個位置的機率為 \(move1 = \frac{move}{n-1} = \frac{2}{n^2}\)
\(dp[i][0] = dp[i - 1][0] \times stay + dp[i - 1][1] \times move1\),不動或者從非一球移動到一球。
\(dp[i][1] = dp[i - 1][0] \times move + dp[i - 1][1] \times (1 - move1)\),從一球移動或者從非一球移動到非移動(全機率-移動到一球的機率)
最後\(p_1 = dp[k][0],p_2 = p_3 = ... = p_n = \frac{dp[k][1]}{n-1}\),答案就是\(\sum_{i=1}^{n} i p_i = dp[k][0] + \frac{(n+2)(n-1)}{2} \frac{dp[k][1]}{n-1}\)。
時間複雜度為\(O(k)\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
long long qpower(long long a, long long b) {
long long qwq = 1;
while (b) {
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
long long inv(long long x) { return qpower(x, mo - 2); }
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, k;
cin >> n >> k;
int invn = inv(n);
int invn1 = inv(n - 1);
int move = 2ll * invn * (n - 1) % mo * invn % mo;
int stay = (1ll - move + mo) % mo;
int move1 = 1ll * move * invn1 % mo;
array<int, 2> dp{};
dp[0] = 1;
for (int i = 0; i < k; i++) {
array<int, 2> dp2{};
dp2[0] = (1ll * dp[0] * stay % mo + 1ll * dp[1] * move1 % mo) % mo;
dp2[1] = (1ll * dp[0] * move % mo +
1ll * dp[1] * ((1 - move1 + mo) % mo) % mo) %
mo;
dp.swap(dp2);
}
int ans =
(dp[0] + 1ll * (n + 2) * (n - 1) / 2 % mo * dp[1] % mo * invn1 % mo) %
mo;
cout << ans << '\n';
return 0;
}
F - InterSections (abc360 F)
題目大意
給定\(n\)個區間,若倆區間\([l_a, r_a],[l_b,r_b]\)有交叉 ,則有\(l_a < l_b < r_a < r_b\)。
設 \(f(l,r)\)表示與 \([l,r]\)有交叉的區間數。
求最大值。
解題思路
<++>
神奇的程式碼
G - Suitable Edit for LIS (abc360 G)
題目大意
給定一個陣列,進行一次操作,使得最長上升子序列的長度最大。
操作為,將任意一個數改為任意一個數。
解題思路
這種有一次修改的,可以從修改處考慮,其修改處左右兩邊是一個正常的最長上升子序列問題。
因此事先求出\(pre[i]\)表示從左到右,選了第 \(i\)個數字的最長上升子序列長度,\(suf[i]\)表示從右到左,選了第 \(i\)個數字的最長下降子序列長度,需要使用\(O(n\log n)\)的方法。考慮如何將 \(pre\)和 \(suf\)組合。
注意到是嚴格上升,如果列舉 \(suf_i\),思考 \(\max(pre_j + suf_i + 1)\)有什麼條件,以及 \(j\)如何找。
列舉 \(i\),考慮 \(j\),則有 \(k \in (j, i), a_k\)需要修改,因此 \(j < i - 1\),同時由於嚴格遞增, \(a_i > a_j + 1\) ,這樣\(a_k\)才能更改成對應的數,使得 \(pre_j\)和 \(suf_i\)拼接起來得到一個更長的最長上升子序列。
即需要解決這麼一個問題\(\max_{j < i - 1 \& a_i > a_j + 1} (pre_j + suf_i + 1)\)。容易發現這就是一個樸素的二維偏序問題,用線段樹求解即可。注意需要對\(a_i\)離散化。
\(O(n \log n)\)的一種最長上升子序列的求法,即\(pos[i]\)表示最長上升子序列長度為 \(i\)的末尾數字(最大數字)的最小值。注意到 \(pos\)是一個遞增的陣列,因此對於當前數字\(a_i\) ,可以二分找到它可以接在哪個數字\(a_j\)的後面,進而知道了當前的\(dp[i]\),然後更新 \(pos\)陣列。或者樸素的 \(O(n^2)\)的 \(dp\),分析轉移式可以注意到也是一個二維偏序問題,也可以用線段樹解決。
用線段樹解二維偏序,下標是\(a_j\),值是\(dp[j]\)。即當前\(dp[j]\)求出來後,將 \(dp[j - 1]\)插入到線段樹裡,即將 \(a_j\)位置的值賦予 \(dp[j]\)。
這樣在求\(dp[i]\)時,線段樹裡的值都是 \(j < i - 1\)的 \(dp[j]\)的值,自然滿足第一個 \(j < i - 1\)的條件,因為線段樹的下標是 \(a_j\),而 \(a_i > a_j + 1\)相當於一個區間 \([1, a_i - 2]\),要求的就是這個區間的\(dp\)最大值,因為線段樹維護的就是\(dp[j]\),故區間查詢最大值、單點修改即可。而由於\(a_i\)很大,為作為線段樹的下標,需要離散化。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int inf = 1e9 + 8;
const int N = 2e5 + 8;
class segment {
#define lson (root << 1)
#define rson (root << 1 | 1)
public:
int maxx[N << 2];
void build(int root, int l, int r) {
if (l == r) {
maxx[root] = 0;
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
maxx[root] = max(maxx[lson], maxx[rson]);
}
void update(int root, int l, int r, int pos, int val) {
if (l == r) {
maxx[root] = max(maxx[root], val);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
update(lson, l, mid, pos, val);
else
update(rson, mid + 1, r, pos, val);
maxx[root] = max(maxx[lson], maxx[rson]);
}
LL query(int root, int l, int r, int L, int R) {
if (L <= l && r <= R) {
return maxx[root];
}
int mid = (l + r) >> 1;
int resl = -inf, resr = -inf;
if (L <= mid)
resl = query(lson, l, mid, L, R);
if (R > mid)
resr = query(rson, mid + 1, r, L, R);
return max(resl, resr);
}
} sg;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a)
cin >> x;
auto solve = [&](vector<int>& a) -> vector<int> {
vector<int> dp(n, 1);
vector<int> pos(n + 1, inf);
pos[0] = -inf;
for (int i = 0; i < n; i++) {
auto it = lower_bound(pos.begin(), pos.end(), a[i]) - pos.begin();
dp[i] = it;
pos[it] = min(pos[it], a[i]);
}
return dp;
};
auto pre = solve(a);
reverse(a.begin(), a.end());
transform(a.begin(), a.end(), a.begin(), [](int x) { return -x; });
auto suf = solve(a);
reverse(suf.begin(), suf.end());
reverse(a.begin(), a.end());
transform(a.begin(), a.end(), a.begin(), [](int x) { return -x; });
vector<int> b(a.begin(), a.end());
b.push_back(-inf);
sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());
auto rank = [&](int x) {
return lower_bound(b.begin(), b.end(), x) - b.begin() + 1;
};
int ans = 0;
int m = b.size();
sg.build(1, 1, m);
for (int i = 0; i < n; ++i) {
int cnt = sg.query(1, 1, m, 1, rank(a[i] - 1) - 1);
ans = max(ans, suf[i] + cnt + (i != 0));
if (i > 0)
sg.update(1, 1, m, rank(a[i - 1]), pre[i - 1]);
}
ans = max(ans, sg.query(1, 1, m, 1, m) + 1);
cout << ans << '\n';
return 0;
}