Codeforces Round 979 div2 個人題解(A~E)
Dashboard - Codeforces Round 979 (Div. 2) - Codeforces
火車頭
#define _CRT_SECURE_NO_WARNINGS 1
#include <algorithm>
#include <array>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#define ft first
#define sd second
#define yes cout << "yes\n"
#define no cout << "no\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define sort_all(x) sort(all(x))
#define sort1_all(x) sort(all1(x))
#define reverse_all(x) reverse(all(x))
#define reverse1_all(x) reverse(all1(x))
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#define RED cout << "\033[91m"
#define GREEN cout << "\033[92m"
#define YELLOW cout << "\033[93m"
#define BLUE cout << "\033[94m"
#define MAGENTA cout << "\033[95m"
#define CYAN cout << "\033[96m"
#define RESET cout << "\033[0m"
// 紅色
#define DEBUG1(x) \
RED; \
cout << #x << " : " << x << endl; \
RESET;
// 綠色
#define DEBUG2(x) \
GREEN; \
cout << #x << " : " << x << endl; \
RESET;
// 藍色
#define DEBUG3(x) \
BLUE; \
cout << #x << " : " << x << endl; \
RESET;
// 品紅
#define DEBUG4(x) \
MAGENTA; \
cout << #x << " : " << x << endl; \
RESET;
// 青色
#define DEBUG5(x) \
CYAN; \
cout << #x << " : " << x << endl; \
RESET;
// 黃色
#define DEBUG6(x) \
YELLOW; \
cout << #x << " : " << x << endl; \
RESET;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pdd;
typedef pair<ll, int> pli;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;
typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;
typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pli> vpli;
typedef vector<pss> vpss;
typedef vector<ti3> vti3;
typedef vector<tl3> vtl3;
typedef vector<tld3> vtld3;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef priority_queue<string> pqs;
typedef priority_queue<pii> pqpii;
typedef priority_queue<psi> pqpsi;
typedef priority_queue<pll> pqpll;
typedef priority_queue<psi> pqpsl;
typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;
typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template <typename T>
inline T read()
{
T x = 0;
int y = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
{
if (ch == '-')
y = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * y;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x >= 10)
{
write(x / 10);
}
putchar(x % 10 + '0');
}
/*#####################################BEGIN#####################################*/
void solve()
{
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 連結:
A. A Gift From Orangutan
A. 來自猩猩的禮物
時間限制:每個測試1秒
記憶體限制:256兆位元組
在叢林探險時,你遇到了一隻戴著領結的稀有猩猩!你和猩猩握手,並給他一些食物和水。作為回報……
猩猩送給你一個長度為 \(n\) 的陣列 \(a\)。使用 \(a\),你將構建兩個陣列 \(b\) 和 \(c\),它們都包含 \(n\) 個元素,方式如下:
每個 \(1 \leq i \leq n\) 對應 \(b_i = \min(a_1, a_2, \ldots, a_i)\)。
每個 \(1 \leq i \leq n\) 對應 \(c_i = \max(a_1, a_2, \ldots, a_i)\)。
將 \(a\) 的得分定義為 \(\sum_{i=1}^{n}(c_i - b_i)\)(即 \(c_i - b_i\) 與所有 \(1 \leq i \leq n\) 之和)。在計算得分之前,您可以隨意打亂 \(a\) 的元素。
找出以最佳方式打亂 \(a\) 的元素時可以獲得的最大得分。
輸入
第一行包含 \(t\) (\(1 \leq t \leq 100\)) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(n\) (\(1 \leq n \leq 1000\)) — \(a\) 中的元素數量。
下一行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) (\(1 \leq a_i \leq 1000\)) — 陣列 \(a\) 的元素。
保證所有測試用例的 \(n\) 之和不超過 \(1000\)。
輸出
對於每個測試用例,輸出你能得到的最高分數。
示例
輸入
3
1
69
3
7 6 5
5
1 1 1 2 2
輸出
0
4
4
備註
在第一個測試用例中,沒有其他方法可以重新排列 \(a\)。因此,\(b = [69]\) 和 \(c = [69]\)。唯一可能的得分是 \(69 - 69 = 0\)。
在第二個測試用例中,你可以將 \(a\) 重新排列為 \([7, 5, 6]\)。這裡,\(b = [7, 5, 5]\) 和 \(c = [7, 7, 7]\)。在這種情況下的得分是 \((7 - 7) + (7 - 5) + (7 - 5) = 4\)。可以證明這是最大可能得分。
解題思路
我們可以獲得的最大極差是最大值\(mx\)減最小值\(mn\),所以只需要找出最大和最小值算出極差然後乘以\(n-1\)即可
程式碼實現
void solve()
{
int n;
cin >> n;
int mx = 0;
int mn = inf;
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
mx = max(mx, x);
mn = min(mn, x);
}
cout << 1ll * (n - 1) * (mx - mn) << endl;
}
B. Minimise Oneness
時間限制:每個測試1.5秒
記憶體限制:256兆位元組
對於任意二進位制字串 \(t\),令 \(f(t)\) 為 \(t\) 中僅包含 \(0\) 的非空子序列的數量,令 \(g(t)\) 為 \(t\) 中至少包含一個 \(1\) 的非空子序列的數量。
我們將二進位制字串 \(t\) 的一元性定義為 \(|f(t)−g(t)|\),其中對於任意整數 \(z\),\(|z|\) 表示 \(z\) 的絕對值。
給定一個正整數 \(n\)。查詢長度為 \(n\) 的二進位制字串 \(s\),使得其一性儘可能小。如果有多個字串,則可以列印其中任何一個。
輸入
第一行包含一個整數 \(t\) (\(1 \leq t \leq 10^4\)) — 測試用例的數量。
每個測試用例的唯一一行包含一個整數 \(n\) (\(1 \leq n \leq 2 \cdot 10^5\)) — \(s\) 的長度。
保證所有測試用例的 \(n\) 的總和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,在新行上輸出 \(s\)。如果存在多個答案,則輸出任意一個。
示例
輸入
3
1
2
3
輸出
0
01
010
備註
在第一個測試用例中,輸出示例中 \(f(t)=1\),因為有一個僅包含 \(0\) 的子序列(\(0\)),而 \(g(t)=0\),因為沒有包含至少一個 \(1\) 的子序列。一元性為 \(|1−0|=1\)。輸出 \(1\) 也是正確的,因為它的一元性為 \(|0−1|=1\)。
在第二個測試用例中,輸出示例中 \(f(t)=1\),因為有一個僅包含 \(0\) 的非空子序列,而 \(g(t)=2\),因為有兩個至少包含一個 \(1\) 的非空子序列(\(01\) 和 \(1\))。因此一元性為 \(|1−2|=1\)。可以證明這是所有可能長度為 \(2\) 的二進位制字串中一元性的最小值。
解題思路
記字串\(t\)中\(0\)的數量為\(n0\),\(1\)的數量為\(n1\),易得\(f(t)=2^{n0}-1\),\(g(t)=2^{n0}\times(2^{n1}-1)\)。
展開 $ g(t) $:
代入 $ g(t) $ 的表示式:
這裡我們可以考慮兩種情況:
-
如果 $ n1 = 0 $,則 $ g(t) = 0 $,此時 $ |f(t) - g(t)| = |2^{n0} - 1| $。
-
如果 $ n1 > 0 $,則 $ 2^{n0 + n1} $ 會大於 $ 2^{n0 + 1} $,所以:
\[|f(t) - g(t)| = 2^{n0 + n1} - 2^{n0 + 1} - 1 \]
最終結果為:
當\(n1=1\)時值最小
程式碼實現
void solve()
{
int n;
cin >> n;
string s(n - 1, '0');
s += "1";
cout << s << endl;
}
C. A TRUE Battle
時間限制:每個測試2秒
記憶體限制:256兆位元組
Alice 和 Bob 正在玩遊戲。有一個包含 \(n\) 個布林值的列表,每個布林值要麼為真,要麼為假,以長度為 \(n\) 的二進位制字串給出(其中 \(1\) 表示真,而 \(0\) 表示假)。最初,布林值之間沒有運算子。
Alice 和 Bob 將輪流在布林值之間放置 and 或 or,Alice 先走。因此,遊戲將由 \(n−1\) 個回合組成,因為有 \(n\) 個布林值。Alice 的目標是讓最後一個語句的計算結果為真,而 Bob 的目標是讓它的計算結果為假。給定布林值列表,確定如果兩個玩家都發揮最佳水平,Alice 是否會獲勝。
要計算最終表示式的值,請重複執行以下步驟,直到語句由單個真或假組成:
如果語句包含 and 運算子,請選擇任意一個並將其周圍的子表示式替換為其計算值。
否則,語句包含 or 運算子。請選擇任意一個並將其周圍的子表示式替換為其計算值。
例如,表示式 true or false and false 的計算結果為 true or (false and false) = true or false = true。可以證明任何複合語句的結果都是唯一的。
輸入
第一行包含 \(t\) (\(1 \leq t \leq 10^4\)) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(n\) (\(2 \leq n \leq 2 \cdot 10^5\)) — 字串的長度。
第二行包含一個長度為 \(n\) 的二進位制字串,由字元 \(0\) 和 \(1\) — 布林值列表組成。
保證所有測試用例的 \(n\) 的總和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,如果 Alice 獲勝,則輸出“YES”(不帶引號),否則輸出“NO”(不帶引號)。
您可以在任何情況下輸出“YES”和“NO”(例如,字串“yES”、“yes”和“Yes”將被識別為肯定響應)。
示例
輸入
5
2
11
3
010
12
101111111100
10
0111111011
8
01000010
輸出
YES
NO
YES
YES
NO
備註
在第一個測試用例中,Alice 可以在兩個布林值之間放置 and。遊戲結束,因為沒有其他地方可以放置運算子,Alice 贏得比賽,因為 true and true 是 true。
在第二個測試用例中,Alice 可以在中間的 true 和左邊的 false 之間放置 or。Bob 可以在中間的 true 和右邊的 false 之間放置 and。語句 false or true and false 是 false。
注意,這些示例可能不是 Alice 或 Bob 的最佳策略。
解題思路
對於在最左邊的1來說,如果我們在其右邊插入一個or,那麼無論右邊最後算出來是什麼結果,都無法改變它,對於最右邊的1同理。
所以如果字串首或尾有1則Yes。
對於子串"11"來說,我們一定可以構成"... or 1 ... 1 or ..."或"... 1 or 1 or ...."的形式使得1無法被消除,所以如果字串存在“11”,則Yes
其它情況則No
程式碼實現
void solve()
{
int n;
cin >> n;
string s;
cin >> s;
if (s.front() == '1' || s.back() == '1' || s.find("11") != string::npos)
YES;
else
NO;
}
D. QED's Favorite Permutation
時間限制:每個測試2秒
記憶體限制:256兆位元組
QED 被賦予一個長度為 \(n\) 的排列 \(p\)。他還有一個長度為 \(n\) 的字串 \(s\),其中僅包含字元 \(L\) 和 \(R\)。QED 只喜歡按非遞減順序排序的排列。要對 \(p\) 進行排序,他可以選擇以下任何操作並執行任意次數:
選擇一個索引 \(i\),使得 \(s_i = L\)。然後,交換 \(p_i\) 和 \(p_{i-1}\)。保證 \(s_1 \neq L\)。
選擇一個索引 \(i\),使得 \(s_i = R\)。然後,交換 \(p_i\) 和 \(p_{i+1}\)。保證 \(s_n \neq R\)。
他還被給予 \(q\) 個查詢。在每個查詢中,他選擇一個索引 \(i\),並將 \(s_i\) 從 \(L\) 更改為 \(R\)(或從 \(R\) 更改為 \(L\))。請注意,這些更改是持久的。
每次查詢後,他都會詢問您是否可以透過執行上述操作任意多次以非遞減順序對 \(p\) 進行排序。請注意,在回答每個查詢之前,排列 \(p\) 會重置為其原始形式。
輸入
第一行包含 \(t\) (\(1 \leq t \leq 10^4\)) — 測試用例的數量。
每個測試用例的第一行包含兩個整數 \(n\) 和 \(q\) (\(3 \leq n \leq 2 \cdot 10^5\), \(1 \leq q \leq 2 \cdot 10^5\)) — 排列的長度和查詢的數量。
下一行包含 \(n\) 個整數 \(p_1, p_2, \ldots, p_n\) (\(1 \leq p_i \leq n\), \(p\) 是排列)。
下一行包含 \(n\) 個字元 \(s_1 s_2 \ldots s_n\)。保證 \(s_i\) 為 \(L\) 或 \(R\)、\(s_1 = R\) 和 \(s_n = L\)。
以下 \(q\) 行包含一個整數 \(i\) (\(2 \leq i \leq n-1\)),表示 \(s_i\) 從 \(L\) 更改為 \(R\)(或從 \(R\) 更改為 \(L\))。
保證所有測試用例的 \(n\) 和 \(q\) 之和不超過 \(2 \cdot 10^5\)。
輸出
對於每個查詢,如果可能則輸出“YES”(不帶引號),否則輸出“NO”(不帶引號)。
您可以在任何情況下輸出“YES”和“NO”(例如,字串“yES”、“yes”和“Yes”將被識別為肯定響應)。
示例
輸入
3
5 3
1 4 2 5 3
RLRLL
2
4
3
8 5
1 5 2 4 8 3 6 7
RRLLRRRL
4
3
5
3
4
6 2
1 2 3 4 5 6
RLRLRL
4
5
輸出
YES
YES
NO
NO
YES
NO
NO
NO
YES
YES
備註
在第一個測試用例中,第一個查詢後的 \(s = RRRLL\)。QED 可以使用以下操作對 \(p\) 進行排序:
最初,\(p = [1, 4, 2, 5, 3]\)。
選擇 \(i=2\) 並將 \(p_2\) 與 \(p_3\) 交換。現在,\(p = [1, 2, 4, 5, 3]\)。
選擇 \(i=5\) 並將 \(p_5\) 與 \(p_4\) 交換。現在,\(p = [1, 2, 4, 3, 5]\)。
選擇 \(i=4\) 並將 \(p_4\) 與 \(p_3\) 交換。現在,\(p = [1, 2, 3, 4, 5]\),這是非遞減順序。
可以證明,第一個測試用例三次更新後,不可能對陣列進行排序。
解題思路
觀察題目可得兩個性質
- 設\(R=0,L=1\),對於任何非遞減序的子段,我們可以任意重排裡面的元素
- 對於任意子陣列\(p_{l},p_{l+1},p_{l+2},\dots,p_{r}\)滿足$l=min(p_i),l\le i\le r \(且\)r=max(p_i),l\le i\le r \(,我們需要重排\)p_i,l\le i\le r$
所以,我們可以根據性質2,對排列\(p\)進行分段,對每一段計算其逆序對數量。算出每段逆序對數總和。如果總和為零,說明可以重排。
每次更改計算一下對該段逆序對的影響,然後加到總和中。
計算逆序對可以使用樹狀樹狀或線段樹。
時間複雜度為\(O(n\log(n))\)
其實維護LR數量就行了,不需要維護逆序對數量。賽時樹狀陣列板子出問題了,寫半天沒找到bug在哪,一直wa2,還以為逆序對算錯了,賽後才發現是板子有問題,人麻了
程式碼實現
struct seg
{
int l, r;
seg() : l(0), r(0) {}
seg(int _l, int _r) : l(_l), r(_r) {}
bool operator<(const seg &rhs) const
{
return l < rhs.l;
}
};
template <typename T>
struct Fenwick
{
int n;
vector<T> a;
Fenwick(int n_ = 0)
{
init(n_);
}
void init(int n_)
{
n = n_;
a.assign(n + 1, T{});
}
void add(int x, const T &v)
{
for (int i = x; i <= n; i += i & -i)
{
a[i] = a[i] + v;
}
}
T sum(int x)
{
T ans{};
for (int i = x; i > 0; i -= i & -i)
{
ans = ans + a[i];
}
return ans;
}
T rangeSum(int l, int r)
{
return sum(r) - sum(l - 1);
}
};
void solve()
{
int n, q;
cin >> n >> q;
vi a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int mx = 0; // 最大值
int l = 1, r = 1; // 左右指標
set<seg> st; // 儲存可排序的區間
for (int i = 1; i <= n; i++, r++)
{
mx = max(mx, a[i]); // 更新最大值
if (mx == r)
{ // 如果當前最大值等於右指標
st.insert({l, r}); // 將區間[l, r]插入集合
l = i + 1; // 更新左指標
}
}
string s;
cin >> s;
s = " " + s;
Fenwick<int> cntR(n + 1); // 維護R的數量
Fenwick<int> cntL(n + 1); // 維護L的數量
for (int i = 1; i <= n; i++)
{
if (s[i] == 'R')
cntR.add(i, 1);
else
cntL.add(i, 1);
}
long long sum = 0; // 統計逆序對的總和
for (auto it : st)
{ // 遍歷可排序的區間
int l = it.l, r = it.r; // 取出區間的左右端點
for (int i = l; i <= r; i++)
{
if (s[i] == 'R') // 如果當前是R
sum += cntL.rangeSum(l, i); // 統計逆序對
}
}
while (q--)
{ // 處理每個查詢
int p; // 查詢的索引
cin >> p; // 輸入查詢索引
auto it = st.upper_bound({p, p}); // 找到p的上界
it = prev(it); // 獲取p的前一個區間
int l = it->l, r = it->r; // 獲取區間的左右端點
if (s[p] == 'R')
{ // 如果當前是R
sum -= cntL.rangeSum(l, p); // 減去當前逆序對
cntL.add(p, 1); // 更新L
cntR.add(p, -1); // 更新R
s[p] = 'L'; // 改變方向
sum += cntR.rangeSum(p, r); // 加上新的逆序對
}
else
{ // 如果當前是L
sum -= cntR.rangeSum(p, r); // 減去當前逆序對
cntL.add(p, -1); // 更新L
cntR.add(p, 1); // 更新R
s[p] = 'R'; // 改變方向
sum += cntL.rangeSum(l, p); // 加上新的逆序對
}
if (sum == 0) // 如果逆序對總數為0
cout << "YES\n"; // 可以排序
else
cout << "NO\n"; // 不能排序
}
}
E. MEXimize the Score
時間限制:每個測試2秒
記憶體限制:256兆位元組
假設我們將陣列 \(b\) 的元素劃分為任意數量 \(k\) 的非空多集 \(S_1, S_2, \ldots, S_k\),其中 \(k\) 為任意正整數。將 \(b\) 的得分定義為 \(b\) 的所有可能分割槽中任意整數 \(k\) 的最大值 \(MEX(S_1) + MEX(S_2) + \ldots + MEX(S_k)\)。
Envy 被賦予一個大小為 \(n\) 的陣列 \(a\)。因為他知道計算 \(a\) 的分數對你來說太容易了,所以他要求你計算 \(a\) 的所有 \(2^{n}-1\) 個非空子序列的分數之和。由於這個答案可能很大,請輸出它對 \(998244353\) 取模的結果。
整數集合 \(c_1, c_2, \ldots, c_k\) 中的 \(MEX\) 定義為集合 \(c\) 中未出現的最小非負整數 \(x\)。例如,\(MEX([0,1,2,2])=3\) 和 \(MEX([1,2,2])=0\)。
如果可以透過刪除幾個(可能是零個或全部)元素從 \(y\) 中獲得 \(x\),則序列 \(x\) 是序列 \(y\) 的子序列。
輸入
第一行包含一個整數 \(t\) (\(1 \leq t \leq 10^4\)) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(n\) (\(1 \leq n \leq 2 \cdot 10^5\)) — \(a\) 的長度。
每個測試用例的第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) (\(0 \leq a_i < n\)) — 陣列 \(a\) 的元素。
保證所有測試用例的 \(n\) 之和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,輸出答案,模數為 \(998244353\)。
示例
輸入
4
3
0 0 1
4
0 0 1 1
5
0 0 1 2 2
4
1 1 1 1
輸出
11
26
53
0
備註
在第一個測試用例中,我們必須考慮七個子序列:
\([0]\):得分為 \(1\)。
\([0]\):得分為 \(1\)。
\([1]\):得分為 \(0\)。
\([0,0]\):得分為 \(2\)。
\([0,1]\):得分為 \(2\)。
\([0,1]\):得分為 \(2\)。
\([0,0,1]\):得分為 \(3\)。
第一個測試用例的答案是 \(1 + 1 + 2 + 2 + 2 + 3 = 11\)。
在最後一個測試用例中,所有子序列的得分都是 \(0\)。
解題思路
觀察發現分數 $ b $ 等價於 $cnt_0 + \min(cnt_0, cnt_1) + \ldots + \min(cnt_0, \ldots, cnt_{n-1}) $,其中 $ cnt_i $ 表示 \(i\)在$ b $的出現次數。
我們可以貪心地構造這 $ k $ 個分割槽,每次選擇最小的 $ j $ 使得 $ cnt_j = 0 $ 且 $ \min(cnt_0, \ldots, cnt_{-1}) > 0 $,得到分割槽 $ [0, 1, \ldots, j-1] $。
這是最優的,因為我們新增的每個元素都會使 MEX 增加 $ 1 $,從而使分數增加 $ 1 $。如果我們新增 $ j $,MEX 不會增加。此外,當我們新增一個元素時,分數的增加不會超過 $ 1 $。新增少於 $ j $ 的元素無法增加未來陣列的 MEX。
所以我們可以列舉分割槽數\(k\),對\(k\)分割槽下的最大\(\text{mex}\)進行貢獻計算
對於\(i\lt \text{mex}\)的方案選擇總數的貢獻為 \(f_i= \sum_{k=j}^{cnt_i} C _{cnt_i}^{k} \cdot f_{i-1}\)
對於\(i\gt mex\)的方案選擇總數的貢獻為\(2^{\sum_{j=mex}^{n}{cnt_j}}\),我們可以使用字尾陣列快速求出
最後把兩者相乘加入答案
程式碼實現
void solve()
{
int n;
cin >> n;
vector<int> cnt(n);
// 統計陣列 a 中每個值的出現頻次
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
cnt[x]++;
}
vector<int> suf(n); // suf 陣列用於儲存從第 i 個位置開始,到陣列末尾的字尾積
suf[n - 1] = 1; // 最後一個元素的字尾積設為 1,因為 suf 陣列是為每個位置計算貢獻做準備的
// 逆序計算每個位置的字尾積, suf[i] 表示從位置 i 到末尾,每個 mex 值增加的貢獻倍數
for (int i = n - 2; i >= 0; i--)
{
suf[i] = suf[i + 1] * qmi(2, cnt[i + 1]) % mod;
}
ll ans = 0; // 最終答案
vector<ll> f(n); // f 陣列用於記錄每個元素對當前 MEX 的貢獻
vector<int> num = cnt; // num 用來逐步減少每個元素的頻次,以此來模擬分割槽過程
int p = 0; // 指標 p 用來追蹤當前 MEX 的上限
// 列舉分割槽數 k,k 從 n 開始逐步減少到 1
for (int k = n; k >= 1; k--)
{
// 找到最大的滿足 cnt[p] >= k 的 p 值,p 用於控制當前可以包含的 mex 最大值
while (p < n && cnt[p] >= k)
{
p++; // p 逐步向右移動,直到找到第一個 cnt[p] < k 的元素
}
ll res = 1; // 當前分割槽中子序列貢獻的乘積,初始為 1
// 遍歷 [0, p-1] 區間的元素,計算它們的貢獻
for (int i = 0; i < p; i++)
{
// num[i] 表示元素 i 還能使用的剩餘次數,如果 num[i] >= k,說明該元素還能進入分割槽
while (num[i] >= k)
{
// 使用組合數計算 cnt[i] 中取出 num[i] 次的方法數,並累加到 f[i] 中
f[i] = (f[i] + C(cnt[i], num[i])) % mod;
num[i]--; // 每次使用完 num[i],它的頻次就減少 1
}
// 將每個元素 i 的貢獻乘以 res,更新 res
res = res * f[i] % mod;
// 將當前分割槽的貢獻加到總答案中,同時乘以 suf[i] 表示字尾的貢獻
ans = (ans + res * suf[i] % mod) % mod;
}
}
cout << ans << endl;
}
這場掉100多分,爆炸