GYM 105350 E
題目描述
給定一個大小為 \(N\) 的陣列 \(A\)。
我們定義一個大小為 \(N\) 的陣列 \(B\) 是有效的當且僅當:
- 對於 \(\forall 1\le i\le N,1\le B_i \le N\),如果從 \(B\) 中移除 \(B_i\),則陣列 \(B\) 恰好有 \(A_i\) 個不同的數。
求有多少個不同的由有效陣列 \(B\) 組成的多重集合。
思路
分類討論。
首先很容易想到,\(A\) 中至多有兩種數字,且兩種數字的差為 \(1\)。因為刪除一個數字後不同數字數量至多減少一。否則輸出 \(0\)。
- 若 \(A\) 中只有一個數字 \(x\),那麼有兩種情況:
- 如果 \(x=N-1\),那麼有可能所有數互不相同,有 \(1\) 種情況。
- 如果 \(2x\le N\),那麼有可能每個數的數量 \(\ge 2\),所以刪掉哪個數都不變。這裡要從 \(N\) 個數選出 \(x\) 個數用,並且每個數數量 \(\ge 2\) 且總和為 \(N\),根據插板法,有 \(C_{N-x-1}^{x-1}\cdot C_N^x\) 種情況。
- 若 \(A\) 中有兩個數 \(x,x-1\),且 \(x-1\) 出現了 \(y\) 次,那麼有三種情況:
- 若 \(x-y\le 0\),則無解,因為總共有 \(x\) 個數,其中有 \(y\) 個數出現了一次,所以無解。
- 若 \(2(x-y)+y>N\),則無解,因為有 \(x-y\) 個數至少出現 \(2\) 次,\(y\) 個數出現 \(1\) 次。
- 否則,我們要從 \(N\) 個數中選出 \(x-y\) 個數,再從 \(N-(x-y)\) 個數中選出 \(y\) 個數,同時那 \(x-y\) 個數每個數出現至少兩次,且總共出現 \(N-y\) 次,透過插板法可知總共有 \(C_{N}^{x-y}\cdot C_{N-x+y}^y\cdot C_{N-x-1}^{x-y-1}\) 種情況。
空間複雜度 \(O(N)\),時間複雜度 \(O(N\log N)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 300001, MOD = 998244353;
int t, n, a[MAXN], cnt, f[MAXN], inv[MAXN], Inv[MAXN];
void work() {
f[0] = inv[1] = Inv[0] = 1;
for(int i = 1; i < MAXN; ++i) {
f[i] = 1ll * f[i - 1] * i % MOD;
inv[i] = (i > 1 ? 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD : 1);
Inv[i] = 1ll * Inv[i - 1] * inv[i] % MOD;
}
}
int C(int n, int m) {
return (n < m ? 0 : 1ll * f[n] * Inv[m] % MOD * Inv[n - m] % MOD);
}
void Solve() {
cin >> n;
set<int> s;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
s.insert(a[i]);
}
if(s.size() > 2 || (s.size() == 2 && *s.begin() != *next(s.begin()) - 1)) {
cout << "0\n";
return;
}
cnt = 0;
for(int i = 1; i <= n; ++i) {
cnt += (a[i] == *s.begin());
}
if(s.size() == 1) {
int ans = (*s.begin() == n - 1), res = *s.begin();
if(2 * res <= n) {
ans = (ans + 1ll * C(n - res - 1, res - 1) * C(n, res) % MOD) % MOD;
}
cout << ans << "\n";
}else {
int res = *next(s.begin()) - cnt;
if(res <= 0 || 2 * res + cnt > n) {
cout << "0\n";
return;
}
cout << 1ll * C(n - cnt - res - 1, res - 1) * C(n, res) % MOD * C(n - res, cnt) % MOD << "\n";
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
work();
for(cin >> t; t--; Solve()) {
}
return 0;
}
GYM 105350 F
題目描述
我們定義一個陣列 \(B\) 的 \(\operatorname{MAD}(B)\) 為至少出現兩次的最大值,如果沒有數出現兩次,則 \(\operatorname{MAD}(B)=0\)。
給定陣列 \(A\),求 \(\sum \limits_{l=1}^{N-1}\sum\limits_{r=l+1}^N \operatorname{MAD}([A_l,A_{l+1},\dots,A_r])\)。
思路
我們考慮固定 \(r\),看對應的 \(l\) 和其 \(\operatorname{MAD}\)。影像如下:
接著我們考慮轉移到 \(r+1\):
這裡高出來了藍色部分是因為 \(A_{r+1}\) 變得出現了兩次,所以在這裡會增加 \(\operatorname{MAD}\) 的值。這個很顯然能用線段樹維護。
空間複雜度 \(O(N)\),時間複雜度 \(O(N\log N)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 200001;
struct Segment_Tree {
int l[MAXN << 2], r[MAXN << 2], lazy[MAXN << 2], id[MAXN << 2], Min[MAXN << 2];
ll sum[MAXN << 2];
void build(int u, int s, int t) {
l[u] = s, r[u] = t, sum[u] = Min[u] = lazy[u] = 0;
if(s == t) {
id[u] = s;
return;
}
int mid = (s + t) >> 1;
build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);
}
void tag(int u, int x) {
sum[u] = max(sum[u], 1ll * x * (r[u] - l[u] + 1)), Min[u] = max(Min[u], x), lazy[u] = max(x, lazy[u]);
}
void pushdown(int u) {
tag(u << 1, lazy[u]), tag((u << 1) | 1, lazy[u]), lazy[u] = 0;
}
void update(int u, int s, int t, int x) {
if(s > t) {
return;
}
if(l[u] >= s && r[u] <= t) {
tag(u, x);
return;
}
pushdown(u);
if(s <= r[u << 1]) {
update(u << 1, s, t, x);
}
if(t >= l[(u << 1) | 1]) {
update((u << 1) | 1, s, t, x);
}
sum[u] = sum[u << 1] + sum[(u << 1) | 1];
Min[u] = min(Min[u << 1], Min[(u << 1) | 1]);
}
int Binary_Search(int u, int x) {
if(Min[u] >= x) {
return 1919810;
}
if(l[u] == r[u]) {
return id[u];
}
pushdown(u);
if(Min[u << 1] < x) {
return Binary_Search(u << 1, x);
}
return Binary_Search((u << 1) | 1, x);
}
ll Getsum(int u, int s, int t) {
if(s > t) {
return 0ll;
}
if(l[u] >= s && r[u] <= t) {
return sum[u];
}
pushdown(u);
return (s <= r[u << 1] ? Getsum(u << 1, s, t) : 0ll) + (t >= l[(u << 1) | 1] ? Getsum((u << 1) | 1, s, t) : 0ll);
}
}tr;
int t, n, a[MAXN];
ll ans;
map<int, int> id;
void Solve() {
cin >> n;
for(int i = 1; i <= n; ++i) {
cin >> a[i];
}
tr.build(1, 1, n);
id.clear();
ans = 0;
for(int i = 1; i <= n; ++i) {
if(id.count(a[i])) {
tr.update(1, tr.Binary_Search(1, a[i]), id[a[i]], a[i]);
}
ans += tr.Getsum(1, 1, i);
id[a[i]] = i;
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
for(cin >> t; t--; Solve()) {
}
return 0;
}
GYM 105350 G
題目描述
給定一個大小為 \(N\),以 \(1\) 為根的樹。最開始,所有結點的權值均為 \(0\)。有以下四種操作:
- 令 \(u\) 子樹內所有權值加 \(x\)。
- 令 \(u\) 所有兒子權值加 \(x\)。
- 查詢 \(u\) 子樹內權值最大值。
- 查詢 \(u\) 所有兒子權值最大值。
思路
由於在 dfs 序中一個結點的兒子是不連續的,所以我們考慮調整一下這個 dfs 序:
- 當遞迴到結點 \(u\) 時,我們先把它的所有兒子丟進 dfs 序裡。
- 然後遞迴到它的所有兒子裡。
這樣很明顯兒子就是連續的了,但子樹卻不一定連續。但透過觀察可以發現,子樹最多會被分成兩個部分:根節點和除了根節點的部分。
所以使用線段樹維護即可。
空間複雜度 \(O(N+M)\),時間複雜度 \(O(Q\log N)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 300005;
struct Segment_Tree {
int l[MAXN << 2], r[MAXN << 2];
ll Max[MAXN << 2], lazy[MAXN << 2];
void build(int u, int s, int t) {
l[u] = s, r[u] = t, Max[u] = lazy[u] = 0;
if(s == t) {
return;
}
int mid = (s + t) >> 1;
build(u << 1, s, mid), build((u << 1) | 1, mid + 1, t);
}
void tag(int u, ll x) {
Max[u] += x, lazy[u] += x;
}
void pushdown(int u) {
tag(u << 1, lazy[u]), tag((u << 1) | 1, lazy[u]), lazy[u] = 0;
}
void update(int u, int s, int t, ll x) {
if(s > t) {
return;
}
if(l[u] >= s && r[u] <= t) {
tag(u, x);
return;
}
pushdown(u);
if(s <= r[u << 1]) {
update(u << 1, s, t, x);
}
if(t >= l[(u << 1) | 1]) {
update((u << 1) | 1, s, t, x);
}
Max[u] = max(Max[u << 1], Max[(u << 1) | 1]);
}
ll Getmax(int u, int s, int t) {
if(s > t) {
return -(ll)(1e18);
}
if(l[u] >= s && r[u] <= t) {
return Max[u];
}
pushdown(u);
return max((s <= r[u << 1] ? Getmax(u << 1, s, t) : -(ll)(1e18)), (t >= l[(u << 1) | 1] ? Getmax((u << 1) | 1, s, t) : -(ll)(1e18)));
}
}tr;
int n, q, dfn[MAXN], sz[MAXN], st[MAXN], End[MAXN], tot = 1;
vector<int> e[MAXN];
void dfs(int u, int fa) {
sz[u] = 1, st[u] = tot + 1;
for(int v : e[u]) {
if(v != fa) {
dfn[v] = ++tot;
}
}
End[u] = tot;
for(int v : e[u]) {
if(v != fa) {
dfs(v, u);
sz[u] += sz[v];
}
}
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> q;
for(int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
tr.build(1, 1, n);
dfn[1] = 1;
dfs(1, 0);
for(int i = 1, op, u, x; i <= q; ++i) {
cin >> op >> u;
if(op == 1) {
cin >> x;
tr.update(1, dfn[u], dfn[u], x), tr.update(1, st[u], st[u] + sz[u] - 2, x);
}else if(op == 2) {
cin >> x;
tr.update(1, st[u], End[u], x);
}else if(op == 3) {
cout << max(tr.Getmax(1, st[u], st[u] + sz[u] - 2), tr.Getmax(1, dfn[u], dfn[u])) << "\n";
}else if(op == 4) {
cout << tr.Getmax(1, st[u], End[u]) << "\n";
}
}
return 0;
}
AT AGC023 F
題目描述
給定一個 \(N\) 個結點的樹,每個結點上都有一個數字 \(0\) 或 \(1\)。你要將這些點排成一行,使得沒有結點在其祖先之前。求這些節點上數的最小逆序對數量。
思路
我們考慮把哪個數放在前面更優