Contest Info
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
6 / 6 | O | O | O | O | Ø | Ø |
- O 在比賽中通過
- Ø 賽后通過
- ! 嘗試了但是失敗了
- - 沒有嘗試
Reply
寫完 \(A\) 發現忘記注冊了,氣得我怒開小號。順便看看從 \(0\) 開始多少把能上紫。希望 \(cf\) 不要不識好歹,少出點數論和幾何。
EF會補的,等我會了再說。
F在補了
這兩天影小姐的池子開了,玩了兩天原神,給影小姐弄了一身破銅爛鐵,F剛補完。米哈游你罪該萬死!
感覺不是很會這個F,好難啊,瞎寫了一點,提供一點思路吧QwQ,教學流也太難了。
Solutions
A. Game
題意:
從點 \(1\) 跳到點 \(n\) ,\('0'\) 點不可跳,相鄰 \('1'\) 點能直接走,最多只能跳一下,問最少跳多少步。
思路:
唯一的坑點就是只能跳一下,記錄下首 \(0\) 和尾 \(0\) 就行。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd();
vector<int> vec;
for (int i = 1; i <= n; ++i) {
int t = rd();
if (!t) vec.push_back(i);
}
int ans = 0;
if (vec.size() == 0) ans = 0;
else ans = vec[vec.size() - 1] - vec[0] + 2;
printf("%d\n", ans);
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
B. Game of Ball Passing
題意:
\(n\)個人傳球,告訴你每個人傳球給別人的次數,問最少幾個球讓輸入合法。
思路:
找到傳球次數最多的人,看下其他人傳球次數的總和。如果這個總和不小於最大值,這 \(n-1\) 個人就可以內部消耗,直到傳球次數和最大值一致,就可以正好傳完,只需要 \(1\) 個球。
其余情況則需要多個球了,因為最理想的情況就是每個人都傳球給最多傳球次數的人,剩下的就讓他自己踢就行。
題目只問我們如何合法,所以我們不需要太關注傳給誰,只關注傳球的數量。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd();
ll sum = 0, maxx = 0;
for (int i = 0; i < n; ++i) {
int t = rd();
if (t > maxx) maxx = t;
sum += t;
}
if (maxx == 0) {
puts("0");
return;
}
ll ans = max(1ll, maxx - (sum - maxx));
printf("%lld\n", ans);
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
C. Weird Sum
題意:
給你 \(n \cdot m\) 的矩陣和每個點的顏色,求相同顏色之間所有無序對的曼哈頓距離和。
思路:
\(dis_{u, v} = |x_u - x_v| + |y_u - y_v|\),我們可以發現曼哈頓距離中的 \(x\) 和 \(y\) 是可以分開來計算的。那么我們把所有相同顏色格子的 \(x\) 和 \(y\) 分開記錄一下,從大到小排序一下,計算每個 \(x\) (\(y\)) 對於距離和的貢獻。
以 \(x\) 為例。設總共有 \(n\) 個格子,當前遍歷到排序后的第 \(i\) 個 \(x\),那么這個 \(x_i\) 在計算曼哈頓距離時,對所有的 \(j\) (\(i < j \leq n\)),都需要計算一次 \(x_i - x_j\)。也就是說,\(x_i\) 需要被加 \(n - i\) 次。同理,需要被前面的數減去 \(i - 1\) 次。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector<int> X[100005], Y[100005];
void solve() {
int n = rd(), m = rd();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
int c = rd();
X[c].push_back(i);
Y[c].push_back(j);
}
}
ll ans = 0;
for (int i = 1; i <= 100000; ++i) sort(X[i].begin(), X[i].end(), greater<int>()), sort(Y[i].begin(), Y[i].end(), greater<int>());
for (int i = 1; i <= 100000; ++i) {
if (X[i].size() <= 1) continue;
for (int j = 0; j < X[i].size(); ++j) {
ans -= 1ll * j * X[i][j];
ans += 1ll * (X[i].size() - j - 1) * X[i][j];
ans -= 1ll * j * Y[i][j];
ans += 1ll * (Y[i].size() - j - 1) * Y[i][j];
}
}
printf("%lld\n", ans);
}
int main() {
int t = 1;
while (t--) solve();
return 0;
}
D. Integral Array
題意:
如果對於一個數組中的任意兩個數 \(x, y\),若 \(y \leq x\),那么 \(\lfloor \frac{x}{y} \rfloor\) 也存在於這個數組中,這個數組就是好數組。
現在給你一個數組和所有數的取值上界 \(c\),請你判斷這個數組是否為好數組。
思路:
按照題意來寫的話,枚舉 \(x\) 和 \(y\),雖然找一個數是否存在可以通過前綴和 \(O(1)\) 找,但枚舉的 \(O(n^2)\) 就已經超時了。這時使用數論分塊,找出所有的 \(\lfloor \frac{x}{y} \rfloor\) ,再判斷除數 \(y\) 的取值區間中有沒有數字存在即可。但這樣的時間復雜度是 \(O(n\sqrt{c})\) 的,也無法接受。
那么我們正難則反,先把 \([1, max]\) 中所有不存在的值取出來(設為 \(b\) 數組),再和有的值(設為 \(a\) 數組)進行計算,看看是否存在 <\(b_i\), \(a_j\)>,使得 \([b_i \cdot a_j, (b_i + 1) \cdot a_j)\) 中的數在數組 \(a\) 中存在。這一步就相當於枚舉所有存在的 \(\lfloor \frac{x}{y} \rfloor\) 和不存在的 \(y\) ,看看是否有非法的 \(x\) 存在。
因為上界 \(c\) 的存在,所以如果數組經過去重的操作之后,最壞情況下 \(1\) ~ \(c\) 都不存在,我們枚舉的時候,及時 break
,枚舉的次數就是 \(c + \frac{c}{2} + \cdots + \frac{c}{c}\),這就是一個調和級數了,所以時間復雜度就是 \(O(clog_c)\),可以通過。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
void solve() {
int n = rd(), c = rd();
vector<int> a(n), pre(c + 1);
for (int i = 0; i < n; ++i) {
a[i] = rd();
}
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
int now = 1;
if (a[0] != 1) {
puts("No");
return;
}
vector<int> notHave;
for (auto v : a) {
pre[v]++;
while (now < v) {
now++;
if (now != v) {
notHave.push_back(now);
}
}
}
for (int i = 1; i <= c; ++i) pre[i] += pre[i - 1];
for (auto u : notHave) {
for (auto v : a) {
ll l = 1ll * u * v;
if (l > c) break;
ll r = min(1ll * (u + 1) * v - 1, (ll)c);
if (pre[r] - pre[l - 1] == 0) continue;
else {
puts("No");
return;
}
}
}
puts("Yes");
}
int main() {
int t = 1;
t = rd();
while (t--) solve();
return 0;
}
E. Tyler and Strings
題意:
給定序列 \(s\) 和 \(t\),現對 \(s\) 進行重新排列,問能排列出多少種本質不同的序列 \(s'\) ,使得 \(s'\) 的字典序小於 \(t\) 。
思路:
前置知識:設數字 \(i\) 出現的數量為 \(cnt_i\) ,數字的最大值為 \(k\) ,那么將這個序列排列成所有本質不同的序列的方案是
相當於對所有數字進行全排列,然后去掉每個數字各自的全排列對方案的重復影響。
要讓字典序小,假設當前在第 \(i\) 位,那么 \(s'\) 和 \(t\) 的前綴要一致,並且 \(s'_i < t_i\),后面就可以任意排列了。所以我們遍歷一遍 \(t\) ,查詢 \(s\) 中有多少數字小於 \(t_i\) ,挨個放上去,再計算 \(\frac{(cnt_1 + cnt_2 + \cdots + (cnt_x - 1) + \cdots + cnt_k)!}{cnt_1! \cdot cnt_2! \cdots (cnt_x - 1)! \cdots cnt_k!}\) ,把這些貢獻加入答案中,最后再把這一位填上 \(t_i\) ,把相應的變量值更改一下,接着遍歷下去就行。注意判斷當前 \(s\) 中已經沒有 \(t_i\) 的情況和 \(n < m\) 的情況。然后注意一下 \(s'\) 可以排列成 \(t\) 的前綴串的情況。
但這樣我們需要枚舉比 \(t_i\) 小的數字,時間復雜度達到了 \(O(min(n, m) \cdot k)\)。我們再考慮遍歷到 \(t_i\) 時產生的貢獻,設當前的\(\frac{(cnt_1 + cnt_2 + \cdots + cnt_k - 1)!}{cnt_1! \cdot cnt_2! \cdots cnt_k!}\) 為 \(now\) ,那么產生的貢獻就是 \(now \cdot cnt_1 + now \cdot cnt_2 + \cdots + now \cdot cnt_{t_i - 1}\),其實就相當於要維護一個數量的前綴和,用一個樹狀數組維護即可。
我們再預處理個逆元,時間復雜度就是 \(O(klog_k)\)。
不知道為什么場上寫不出來,感覺自己變笨了。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
ll f[200005], inv[200005];
ll powmod(ll a, ll b) {
ll res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
struct Bit {
int n;
vector<int> bit;
void init(int N) {
n = N;
bit.clear();
bit.resize(N + 1, 0);
}
int lowbit(int x) {
return x & -x;
}
void add(int i, int x) {
while (i <= n) {
bit[i] += x;
i += lowbit(i);
}
}
int query(int i) {
int ans = 0;
while (i) {
ans += bit[i];
i -= lowbit(i);
}
return ans;
}
}b;
void solve() {
int n = rd(), m = rd();
vector<int> s(n + 1), t(m + 1), cnt(200005);
b.init(200000);
for (int i = 1; i <= n; ++i) s[i] = rd(), b.add(s[i], 1), cnt[ s[i] ]++;
for (int i = 1; i <= m; ++i) t[i] = rd();
ll nowInv = 1;
for (int i = 1; i <= 200000; ++i) nowInv = nowInv * inv[ cnt[i] ] % mod;
ll ans = 0;
int exAdd = 1;
for (int i = 1; i <= n; ++i) {
if (i > m) break;
ans += f[n - i] * b.query(t[i] - 1) % mod * nowInv % mod;
if (cnt[ t[i] ] == 0) {
exAdd = 0;
break;
}
nowInv = nowInv * cnt[ t[i] ] % mod;
cnt[ t[i] ]--;
b.add(t[i], -1);
}
if (n >= m) exAdd = 0;
ans = (ans + exAdd) % mod;
printf("%lld\n", ans);
}
int main() {
f[0] = 1;
for (int i = 1; i <= 200000; ++i) f[i] = f[i - 1] * i % mod;
inv[200000] = powmod(f[200000], mod - 2);
for (int i = 200000; i >= 1; --i) inv[i - 1] = inv[i] * i % mod;
int t = 1;
while (t--) solve();
return 0;
}
F. Serious Business
題意:
給定 \(3 \times n\) 的矩陣,你要從點 \((1, 1)\) 走到 點 \((3, n)\) ,每次只能向下或者向右走,權值和為一路的點權和。一開始第二行所有點都不能走,你有 \(q\) 次操作,你可以選擇執行其中若干次操作,使你擁有的權值減少 \(k_i\) ,讓第二行 \([l_i, r_i]\) 成為可走點。問權值最大為多少。
思路:
理解一下題意,就是第一行連續走幾步,然后向下走一步,再在第二行連續走幾步,再向下走一步,最后走到底就行。假設第一行走到點 \((1, i)\) ,第二行走到點 \((2, j)\) ,那么權值花費就是
其中 \(cost_{i, j}\) 代表將第二行的 \([i,j]\) 區間內的各自都變為可走的最小花費。
一看數據范圍,估計要遍歷 \(i, j\) 的其中一維,然后快速地從決策集合中取出最優解。
首先點 \(i, j\) 必須都在操作區間內,這提示我們要遍歷所有的操作區間,在操作區間內找到所有的合法轉移,那么 \(i\) 應該代表一個最優前綴, \(j\) 代表一個最優后綴,而且第一行和第三行的權值也確實是相應的前后綴,只剩下第二行的權值是一個區間權值和,而且對於轉移有影響。那么區間權值和不就是前綴相減嗎?
這里有個費用提前的 \(trick\) ,即在所有點 \(i\) 的狀態中減去第二行的前綴和,在所有點 \(j\) 的前綴和中加上第二行的前綴和。這樣前綴還是只有一個狀態,並且我就可以直接在后綴中取出最大值,保證他是利用當前操作區間時的最優解。
我們發現前綴不受操作的影響,所以嘗試從后往前遍歷,進行轉移。
假設當前遍歷到點 \(ql\) ,我們再遍歷所有以點 \(ql\) 作為左區間的操作,如果點 \(i\) 在當前區間,那么求出最大值后,減去操作的花費 \(k\) 就是答案了。如果要進行轉移,如何影響前面的操作區間呢?因為每行走的格子是連續的,所以點 \(i - 1\) 必須能用到你轉移過來的后綴才行,所以我們將區間最優后綴取出來,減去當前區間的花費 \(k\) ,和之前計算出來的 \(i - 1\) 的后綴取個最大值即可。
以上操作就可以用線段樹來維護了。注意合並的時候要先用左區間的前綴和右區間的后綴合並,更新最優解,不然先對前綴后進行操作,可能造成左區間的后綴和右區間的前綴進行合並,進行非法轉移。
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e5 + 7;
ll f[2][N], a[3][N];
int n, q;
vector< pair<int, ll> > Q[N];
#define ls (id << 1)
#define rs (id << 1 | 1)
#define mid (l + r >> 1)
struct Seg {
struct Tree {
ll maxx, pre, suf;
} t[N << 2];
Tree merge(Tree a, Tree b) {
a.maxx = max({a.maxx, b.maxx, a.pre + b.suf});
a.pre = max(a.pre, b.pre);
a.suf = max(a.suf, b.suf);
return a;
}
void modify(int id, int l, int r, int p) {
if (l == r) {
t[id] = {f[0][l] + f[1][l], f[0][l], f[1][l]};
return;
}
if (p <= mid) modify(ls, l, mid, p);
else modify(rs, mid + 1, r, p);
t[id] = merge(t[ls], t[rs]);
}
Tree query(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return {-INF, -INF, -INF};
if (ql <= l && r <= qr) return t[id];
return merge(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr));
}
pair<ll, ll> getMax(int ql, int qr) {
Tree tmp = query(1, 1, n, ql, qr);
return {tmp.maxx, tmp.suf};
}
} seg;
#undef mid
void solve() {
n = rd(), q = rd();
for (int dd = 0; dd < 3; ++dd) {
for (int i = 1; i <= n; ++i) {
a[dd][i] = rd();
}
}
for (int i = 1; i <= n; ++i) {
f[0][i] = f[0][i - 1] + a[0][i] - a[1][i - 1];
f[1][i] = f[1][i - 1] + a[1][i];
}
ll tmp = 0;
for (int i = n; i >= 1; --i) {
tmp += a[2][i];
f[1][i] += tmp;
}
for (int i = 1; i <= q; ++i) {
int l = rd(), r = rd(), k = rd();
Q[l].push_back({r, k});
}
ll ans = -INF;
for (int ql = n; ql >= 1; --ql) {
seg.modify(1, 1, n, ql);
for (auto [qr, k] : Q[ql]) {
auto [maxx, maxSuf] = seg.getMax(ql, qr);
ans = max(ans, maxx - k);
f[1][ql - 1] = max(f[1][ql - 1], maxSuf - k);
}
}
printf("%lld\n", ans);
}
int main() {
int t = 1;
while (t--) solve();
return 0;
}