Codeforces Rund 976 div2 個人題解(A~E)
Dashboard - Codeforces Round 976 (Div. 2) and Divide By Zero 9.0 - 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. Find Minimum Operations
每個測試的時間限制:1秒
每個測試的記憶體限制:256兆位元組
輸入:標準輸入
輸出:標準輸出
給定兩個整數\(n\)和\(k\)。
在一次操作中,你可以從\(n\)中減去任何\(k\)的冪。具體來說,在一次操作中,你可以將\(n\)替換為\((n - k^x)\),其中\(x\)是非負整數。
找出將\(n\)變為\(0\)所需的最小操作次數。
輸入
每個測試包含多個測試用例。第一行是測試用例的數量\(t\)(\(1 \le t \le 10^4\))。後面是每個測試用例的描述。
每個測試用例的唯一一行包含兩個整數\(n\)和\(k\)(\(1 \le n, k \le 10^9\))。
輸出
對於每個測試用例,輸出每行所需的最小操作次數。
示例
輸入
6
5 2
3 5
16 4
100 3
6492 10
10 1
輸出
2
3
1
4
21
10
注意
在第一個測試用例中,\(n = 5\)和\(k = 2\)。我們可以執行以下操作序列:
- 從\(5\)中減去\(2^0 = 1\),當前值變為\(5 - 1 = 4\)。
- 從\(4\)中減去\(2^2 = 4\),當前值變為\(4 - 4 = 0\)。
可以證明在少於\(2\)次操作的情況下無法將\(n\)變為\(0\)。因此,答案是\(2\)。
在第二個測試用例中,\(n = 3\)和\(k = 5\)。我們可以執行以下操作序列:
- 從\(3\)中減去\(5^0 = 1\),當前值變為\(3 - 1 = 2\)。
- 從\(2\)中減去\(5^0 = 1\),當前值變為\(2 - 1 = 1\)。
- 從\(1\)中減去\(5^0 = 1\),當前值變為\(1 - 1 = 0\)。
可以證明在少於\(3\)次操作的情況下無法將\(n\)變為\(0\)。因此,答案是\(3\)。
解題思路
-
特殊情況處理:
- 當\(k = 1\)時,我們只能減去\(1\)(即\(1^0\)),因此將\(n\)減到\(0\)需要\(n\)次操作。
-
計算\(k\)的冪:
- 對於\(k > 1\),我們需要計算出所有可能的\(k^x\)(直到\(k^x\)超過\(10^9\)),並將這些值儲存在一個列表中。
-
貪心演算法:
- 從最大的\(k^x\)開始,優先使用較大的冪來減少\(n\),每次減去\(k^x\)的最大倍數,直到無法再減去為止。
- 繼續使用下一個較小的\(k^x\),直到\(n\)減到\(0\)。
-
輸出結果:
- 對於每個測試用例,輸出所需的最小操作次數。
程式碼實現
void solve()
{
ll n, k; // 定義變數 n 和 k
cin >> n >> k; // 輸入 n 和 k
if (k == 1) // 特殊情況處理
{
cout << n << endl; // 當 k 為 1 時,操作次數為 n
return; // 結束函式
}
vl v; // 定義一個列表 v 用於儲存 k 的冪
v.pb(1); // 初始化列表,新增 k^0 = 1
for (ll i = 1; v.back() <= 1e9; i++) // 計算 k 的冪,直到超過 10^9
{
v.pb(v.back() * k); // 將下一個 k 的冪新增到列表中
}
ll ans = 0; // 初始化操作次數為 0
for (int i = v.size() - 1; i >= 0; i--) // 從最大的 k 的冪開始
{
ll t = v[i]; // 當前的 k 的冪
if (n >= t) // 如果 n 大於等於當前的 k 的冪
{
ans += n / t; // 計算可以減去多少次當前的 k 的冪
n %= t; // 更新 n,減去已使用的部分
}
}
cout << ans << endl; // 輸出所需的最小操作次數
}
B. Brightness Begins
每個測試的時間限制:1秒
每個測試的記憶體限制:256兆位元組
輸入:標準輸入
輸出:標準輸出
想象一下你有\(n\)個燈泡,編號為\(1, 2, \ldots, n\)。最初,所有燈泡都是開啟的。翻轉一個燈泡的狀態意味著如果它是開啟的,就將其關閉;如果它是關閉的,就將其開啟。
接下來,你將執行以下操作:
- 對於每個\(i = 1, 2, \ldots, n\),翻轉所有\(j\)的狀態,其中\(j\)是\(i^\dagger\)的倍數。
執行完所有操作後,將會有幾個燈泡仍然是開啟的。你的目標是使這些燈泡的數量恰好為\(k\)。
找出滿足條件的最小\(n\),使得在執行操作後正好有\(k\)個燈泡開啟。我們可以證明總是存在答案。
\(^\dagger\)整數\(x\)被\(y\)整除,當且僅當存在一個整數\(z\),使得\(x = y \cdot z\)。
輸入
每個測試包含多個測試用例。第一行是測試用例的數量\(t\)(\(1 \le t \le 10^4\))。後面是每個測試用例的描述。
每個測試用例的唯一一行包含一個整數\(k\)(\(1 \le k \le 10^{18}\))。
輸出
對於每個測試用例,輸出\(n\)— 最小的燈泡數量。
示例
輸入
3
1
3
8
輸出
2
5
11
注意
在第一個測試用例中,最小的燈泡數量是\(2\)。我們用陣列表示所有燈泡的狀態,其中\(1\)表示開啟,\(0\)表示關閉。最初,陣列為\([1, 1]\)。
- 執行\(i = 1\)的操作後,陣列變為\([\underline{0}, \underline{0}]\)。
- 執行\(i = 2\)的操作後,陣列變為\([0, \underline{1}]\)。
最後,開啟的燈泡數量為\(k = 1\)。我們還可以證明答案不能小於\(2\)。
在第二個測試用例中,最小的燈泡數量是\(5\)。最初,陣列為\([1, 1, 1, 1, 1]\)。
- 執行\(i = 1\)的操作後,陣列變為\([\underline{0}, \underline{0}, \underline{0}, \underline{0}, \underline{0}]\)。
- 執行\(i = 2\)的操作後,陣列變為\([0, \underline{1}, 0, \underline{1}, 0]\)。
- 執行\(i = 3\)的操作後,陣列變為\([0, 1, \underline{1}, 1, 0]\)。
- 執行\(i = 4\)的操作後,陣列變為\([0, 1, 1, \underline{0}, 0]\)。
- 執行\(i = 5\)的操作後,陣列變為\([0, 1, 1, 0, \underline{1}]\)。
最後,開啟的燈泡數量為\(k = 3\)。我們還可以證明答案不能小於\(5\)。
解題思路
-
燈泡狀態翻轉:
- 每個燈泡的狀態在每次操作中會被翻轉。具體來說,燈泡\(j\)在操作\(i\)時被翻轉當且僅當\(j\)是\(i\)的倍數。
- 經過所有操作後,燈泡\(j\)的狀態最終取決於\(j\)的因子數量。具體地,若\(j\)的因子數量為奇數,則燈泡\(j\)將處於開啟狀態;若為偶數,則燈泡\(j\)將處於關閉狀態。
-
完全平方數的性質:
- 只有完全平方數的因子數量是奇數。因此,開啟的燈泡數量等於小於或等於\(n\)的完全平方數的數量。
- 設\(m\)為小於或等於\(n\)的最大完全平方數的平方根,則開啟的燈泡數量為\(m\)。
-
確定\(n\):
- 我們需要找到最小的\(n\),使得小於或等於\(n\)的完全平方數的數量\(m\)等於\(k\)。
- 這意味著我們需要滿足\(m = k\),所以\(n\)應該是\(m^2\)和\(m^2 + 2m\)之間的值。
透過以上分析,我們可以使用二分查詢來快速找到滿足條件的最小\(n\)。
程式碼實現
ll floor_sqrt(ll n)
{
ll res = sqrt((ld)n); // 計算 n 的平方根的整數部分
while ((res + 1) * (res + 1) <= n) // 確保 res 是最大的整數,使得 res^2 <= n
res++;
while (res * res > n) // 確保 res 是最小的整數,使得 res^2 <= n
res--;
return res; // 返回 n 的整數平方根
}
void solve()
{
ll k; // 宣告變數 k
cin >> k; // 讀取輸入的 k
ll l = k; // 二分查詢的左邊界
ll r = k + inf; // 二分查詢的右邊界,inf 是一個足夠大的數
ll ans = -1; // 初始化答案為 -1
while (l < r) // 當左邊界小於右邊界時繼續迴圈
{
ll mid = (l + r) / 2; // 計算中間值 mid
ll sqr = floor_sqrt(mid); // 計算 mid 的平方根
ll diff = mid - sqr; // 計算 mid 與其平方根的差值
if (diff < k) // 如果差值小於 k,說明需要更大的 n
l = mid + 1;
else
r = mid;
}
cout << l << "\n"; // 輸出結果
}
C. Bitwise Balancing
每個測試的時間限制:2秒
每個測試的記憶體限制:256兆位元組
輸入:標準輸入
輸出:標準輸出
給定三個非負整數\(b\),\(c\), 和\(d\)。
請找到一個非負整數\(a\),使得\((a | b) - (a \& c) = d\),其中\(|\)和 & 分別表示按位或操作和按位與操作。
如果這樣的\(a\)存在,輸出它的值。如果沒有解,輸出整數\(-1\)。如果有多個解,輸出任意一個。
輸入
每個測試包含多個測試用例。第一行是測試用例的數量\(t\)(\(1 \le t \le 10^5\))。後面是每個測試用例的描述。
每個測試用例的唯一一行包含三個正整數\(b\),\(c\), 和\(d\)(\(0 \le b, c, d \le 10^{18}\))。
輸出
對於每個測試用例,輸出\(a\)的值,或者\(-1\)如果沒有解決方案。
示例
輸入
3
2 2 2
4 2 6
10 2 14
輸出
0
-1
12
注意
在第一個測試用例中,\((0\,|\,2)-(0\,\&\,2)=2-0=2\)。因此,\(a = 0\)是一個正確答案。
在第二個測試用例中,沒有任何值的\(a\)滿足該方程。
在第三個測試用例中,\((12\,|\,10)-(12\,\&\,2)=14-0=14\)。因此,\(a = 12\)是一個正確答案。
解題思路
-
按位操作分析:
- 透過分析按位或和按位與的性質,我們可以將問題轉化為逐位處理。
- 對於每一位\(i\),我們將\(b\),\(c\), 和\(d\)的第\(i\)位分別表示為\(\text{dig}_b\),\(\text{dig}_c\), 和\(\text{dig}_d\)。
- 我們需要決定\(a\)的第\(i\)位\(a_i\)是 0 還是 1,以滿足上述等式。
-
狀態轉移:
- 根據\(a_i\)的取值(0 或 1),我們可以計算出\((a_i | \text{dig}_b)\)和\((a_i \& \text{dig}_c)\)的值,並透過這些值計算出當前位的差異。
- 透過維護一個進位\(\text{dig}\),我們可以有效地處理每一位的狀態。
-
條件判斷:
- 在每一位的處理過程中,我們需要檢查是否存在合適的\(a_i\)使得條件成立。
- 如果在任何一位無法找到合適的\(a_i\),則該測試用例返回\(-1\)。
程式碼實現
const int N = 64; // 定義位數為64位
void solve()
{
ll _b, _c, _d;
cin >> _b >> _c >> _d;
bitset<N> b(_b);
bitset<N> c(_c);
bitset<N> d(_d);
bitset<N> a;
int dig = 0; // 初始化進位
bool flag1 = true; // 標記是否找到解
// 遍歷每一位
for (int i = 0; i < N; ++i)
{
int dig_b = b[i]; // 讀取 b 的第 i 位
int dig_c = c[i]; // 讀取 c 的第 i 位
int dig_d = d[i]; // 讀取 d 的第 i 位
bool flag2 = false; // 標記是否找到當前位的解
// 嘗試 a_i 為 0 或 1
for (int a_i = 0; a_i <= 1; ++a_i)
{
int or_d = a_i | dig_b; // 計算當前位的或結果
int and_d = a_i & dig_c; // 計算當前位的與結果
int diff_d = or_d - and_d; // 計算差值
int tot = diff_d + dig; // 計算總和
// 檢查條件
if (tot < dig_d)
continue; // 如果總和小於 dig_d,繼續
if ((tot - dig_d) % 2 != 0)
continue; // 如果差值不是偶數,繼續
int t = (tot - dig_d) / 2; // 計算進位
if (t < 0 || t > 1)
continue; // 如果進位不合法,繼續
if (a_i == 1)
a.set(i); // 設定 a 的第 i 位
dig = t; // 更新進位
flag2 = true; // 找到當前位的解
break; // 退出迴圈
}
// 如果當前位沒有解,設定標記為 false
if (!flag2)
{
flag1 = false;
break; // 退出位迴圈
}
}
// 檢查最終進位是否為 0
if (dig != 0)
flag1 = false;
// 輸出結果
if (flag1)
{
ll ans = 0; // 初始化答案
for (int i = 0; i < N; ++i)
{
if (a[i]) // 如果 a 的第 i 位為 1
{
ans |= (1LL << i); // 更新答案
}
}
cout << ans << "\n"; // 輸出答案
}
else
cout << "-1\n"; // 輸出 -1
}
D. Connect the Dots
每個測試的時間限制:2秒
每個測試的記憶體限制:512兆位元組
輸入:標準輸入
輸出:標準輸出
在一個美好的傍晚,愛麗絲坐下來玩經典遊戲“連線點”,但這次有些變化。
為了玩這個遊戲,愛麗絲在一條直線上標記了\(n\)個點,索引從\(1\)到\(n\)。最初,這些點之間沒有任何連線,因此它們都是分開的。之後,愛麗絲執行了\(m\)次操作,每次操作如下:
- 她選擇三個整數\(a_i\),\(d_i\)(\(1 \le d_i \le 10\)),和\(k_i\)。
- 她選擇點\(a_i, a_i + d_i, a_i + 2d_i, a_i + 3d_i, \ldots, a_i + k_i \cdot d_i\),並用弧連線這些點之間的每一對。
執行完所有\(m\)次操作後,她想知道這些點形成了多少個聯通塊\(^\dagger\)。請幫助她找到這個數量。
\(^\dagger\)如果兩個點之間透過多個(可能為零)弧和其他點存在路徑,則認為它們在一個聯通塊中。
輸入
每個測試包含多個測試用例。第一行是測試用例的數量\(t\)(\(1 \le t \le 10^5\))。後面是每個測試用例的描述。
每個測試用例的第一行包含兩個整數\(n\)和\(m\)(\(1 \le n \le 2 \cdot 10^5, 1 \le m \le 2 \cdot 10^5\))。
接下來的\(m\)行,每行包含三個整數\(a_i\),\(d_i\), 和\(k_i\)(\(1 \le a_i \le a_i + k_i \cdot d_i \le n, 1 \le d_i \le 10, 0 \le k_i \le n\))。
保證所有測試用例中\(n\)和\(m\)的總和不超過\(2 \cdot 10^5\)。
輸出
對於每個測試用例,輸出聯通塊的數量。
示例
輸入
3
10 2
1 2 4
2 2 4
100 1
19 2 4
100 3
1 2 5
7 2 6
17 2 31
輸出
2
96
61
注意
在第一個測試用例中,\(n = 10\)。第一次操作連線了點\(1\),\(3\),\(5\),\(7\), 和\(9\)。第二次操作連線了點\(2\),\(4\),\(6\),\(8\), 和\(10\)。因此有兩個聯通塊:\(\{1, 3, 5, 7, 9\}\)和\(\{2, 4, 6, 8, 10\}\)。
在第二個測試用例中,\(n = 100\)。唯一的操作連線了點\(19\),\(21\),\(23\),\(25\), 和\(27\)。現在它們形成一個大小為\(5\)的聯通塊。其他\(95\)個點形成單點聯通塊。因此,答案是\(1 + 95 = 96\)。
在第三個測試用例中,\(n = 100\)。經過操作,所有從\(1\)到\(79\)的奇數點將形成一個大小為\(40\)的聯通塊。其他\(60\)個點形成單點聯通塊。因此,答案是\(1 + 60 = 61\)。
解題思路
注意到\(d<=10\),所以最多隻有\(10×10=100\)種操作狀態\((10種起始點×10種步長)\),所以我們可以把所有操作離線出來並進行分類。
我們可以將每個操作視為一個線段,左邊界為起始點\(a_i\),右邊界為\(a_i + k_i \cdot d_i\),然後對同一類的操作進行線段合併。先對線段集按照左邊界排序,如果下一個線段的右邊界小於等於左邊界,就就把它合併進來。
經過這些操作,我們可以大幅度減少操作次數。每一類中單次操作次數越多越容易和別的操作合併,最後均攤下來每種型別的操作次數為\(\frac{n}{d}\),\(d\)為這一類操作的步長。
總和時間複雜度大概為\(T(100\times \frac{m}{100}\log\frac{m}{100}+m)\)
程式碼實現
struct DSU
{
vector<int> parent; // 父節點陣列
vector<int> rank; // 秩(樹的高度)
// 建構函式,初始化父節點和秩
DSU(int n) : parent(n + 1), rank(n + 1, 1)
{ // 點的編號從1到n
for (int i = 1; i <= n; ++i)
{
parent[i] = i; // 初始時每個點的父節點是它自己
}
}
// 查詢根節點,帶路徑壓縮
int find_set(int x)
{
if (parent[x] != x)
parent[x] = find_set(parent[x]); // 遞迴查詢並進行路徑壓縮
return parent[x];
}
// 合併兩個集合,按秩合併
void union_set(int x, int y)
{
int fx = find_set(x); // 查詢x的根節點
int fy = find_set(y); // 查詢y的根節點
if (fx == fy)
return; // 已經在同一個集合中
if (rank[fx] < rank[fy])
{
parent[fx] = fy; // 將fx的父節點設為fy
}
else
{
parent[fy] = fx; // 將fy的父節點設為fx
if (rank[fx] == rank[fy])
rank[fx]++; // 若秩相同,則fx的秩加1
}
}
};
#define l first
#define r second
void solve()
{
int n, m;
cin >> n >> m;
DSU dsu(n); // 初始化並查集
vector<vector<vpii>> opts(11, vector<vpii>(10, vpii())); // 儲存操作的線段
for (int i = 0; i < m; ++i)
{
int a, d, k;
cin >> a >> d >> k;
int r = a % d; // 計算餘數
opts[d][r].pb({a, a + k * d}); // 將操作存入對應的線段
}
for (int i = 1; i <= 10; ++i)
{
for (int j = 0; j < 10; ++j)
{
if (opts[i][j].size() < 2)
continue; // 如果線段少於2個,則跳過
sort(all(opts[i][j])); // 按左邊界排序
int sz = opts[i][j].size();
vpii temp;
temp.eb(opts[i][j][0].l, opts[i][j][0].r); // 初始化第一個線段
for (int k = 1; k < sz; k++)
{
// 合併重疊的線段
if (temp.back().r < opts[i][j][k].l)
temp.eb(opts[i][j][k].l, opts[i][j][k].r);
else
temp.back().r = max(temp.back().r, opts[i][j][k].r);
}
opts[i][j] = temp; // 更新合併後的線段
}
}
for (int i = 1; i <= 10; ++i)
{
for (int j = 0; j < 10; ++j)
{
for (auto &[l, r] : opts[i][j])
{
// 對每個合併後的線段進行連線
for (int k = l; k + i <= min(n, r); k += i)
{
dsu.union_set(k, k + i); // 合併相鄰的點
}
}
}
}
set<int> st; // 用於儲存不同的根節點
for (int i = 1; i <= n; ++i)
{
st.insert(dsu.find_set(i)); // 查詢每個點的根節點並存入集合
}
cout << st.size() << '\n'; // 輸出聯通塊的數量
}
E. Expected Power
每個測試的時間限制:4秒
每個測試的記憶體限制:256兆位元組
輸入:標準輸入
輸出:標準輸出
給定一個陣列\(n\)整數\(a_1,a_2,\ldots,a_n\),以及一個陣列\(p_1, p_2, \ldots, p_n\)。
設\(S\)為隨機 多重集(即可能包含相同元素),構造過程如下:
- 初始時\(S\)是空的。
- 對於每個\(i\)從\(1\)到\(n\),以機率\(\frac{p_i}{10^4}\)將\(a_i\)插入\(S\)。請注意,每個元素是獨立插入的。
定義\(f(S)\)為所有\(S\)元素的 按位異或。請計算\((f(S))^2\)的期望值,並輸出答案模\(10^9 + 7\)。
形式上,設\(M = 10^9 + 7\)。可以證明答案可以表示為一個不可約分數\(\frac{p}{q}\),其中\(p\)和\(q\)是整數且\(q \not \equiv 0 \pmod{M}\)。輸出等於\(p \cdot q^{-1} \bmod M\)的整數。換句話說,輸出一個整數\(x\)使得\(0 \le x \le M\)且\(x \cdot q \equiv p \pmod{M}\)。
輸入
每個測試包含多個測試用例。第一行是測試用例的數量\(t\)(\(1 \le t \le 10^4\))。後面是每個測試用例的描述。
每個測試用例的第一行包含一個整數\(n\)(\(1 \le n \le 2 \cdot 10^5\))。
第二行包含\(n\)個整數\(a_1,a_2,\ldots,a_n\)(\(1 \le a_i \le 1023\))。
第三行包含\(n\)個整數\(p_1,p_2,\ldots,p_n\)(\(1 \le p_i \le 10^4\))。
保證所有測試用例中\(n\)的總和不超過\(2 \cdot 10^5\)。
輸出
對於每個測試用例,輸出期望值\((f(S))^2\)的結果,模\(10^9 + 7\)。
示例
輸入
4
2
1 2
5000 5000
2
1 1
1000 2000
6
343 624 675 451 902 820
6536 5326 7648 2165 9430 5428
1
1
10000
輸出
500000007
820000006
280120536
注意
在第一個測試用例中,\(a = [1, 2]\),每個元素以機率\(\frac{1}{2}\)插入到\(S\),因此\(S\)有\(4\)種可能的結果,每種結果的機率為\(\frac{1}{4}\):
- \(S = \varnothing\),則\(f(S) = 0\),\((f(S))^2 = 0\)。
- \(S = \{1\}\),則\(f(S) = 1\),\((f(S))^2 = 1\)。
- \(S = \{2\}\),則\(f(S) = 2\),\((f(S))^2 = 4\)。
- \(S = \{1,2\}\),則\(f(S) = 1 \oplus 2 = 3\),\((f(S))^2 = 9\)。
因此答案為\(0 \cdot \frac{1}{4} + 1 \cdot \frac{1}{4} + 4 \cdot \frac{1}{4} + 9 \cdot \frac{1}{4} = \frac{14}{4} = \frac{7}{2} \equiv 500\,000\,007 \pmod{10^9 + 7}\)。
在第二個測試用例中,\(a = [1, 1]\),\(a_1\)以機率\(0.1\)插入\(S\),而\(a_2\)以機率\(0.2\)插入\(S\)。\(S\)有\(3\)種可能的結果:
- \(S = \varnothing\),則\(f(S) = 0\),\((f(S))^2 = 0\),機率為\(0.72\)。
- \(S = \{1\}\),則\(f(S) = 1\),\((f(S))^2 = 1\),機率為\(0.26\)。
- \(S = \{1, 1\}\),則\(f(S) = 0\),\((f(S))^2 = 0\),機率為\(0.02\)。
因此答案為\(0 \cdot 0.72 + 1 \cdot 0.26 + 0 \cdot 0.02 = 0.26 = \frac{26}{100} \equiv 820\,000\,006 \pmod{10^9 + 7}\)。
解題思路
題目大意
給定一個整數陣列\(a_1, a_2, \ldots, a_n ,a_i\le 1023\)和機率陣列\(p_1, p_2, \ldots, p_n\),每個元素\(a_i\)有機率\(\frac{p_i}{10^4}\)被選入集合\(S\)。集合\(S\)是一個多重集,允許有重複元素。定義\(f(S)\)為集合\(S\)中所有元素的按位異或(XOR)值。需要計算\(E[(f(S))^2]\)的期望值,並在\(10^9 + 7\)下取模。
推導過程
為了計算\(E[(f(S))^2]\),我們可以利用期望的線性性質,將其展開為不同\(x\)值的機率加權和:
其中,\(P(f(S) = x)\)表示集合\(S\)中元素異或結果為\(x\)的機率。由於\(a_i \leq 1023\),異或結果\(x\)的範圍為\([0, 1023]\)。
狀態轉移推導
注意到\(x\)的範圍比較小,所以我們可以用子序列類\(dp\)的思想暴力列舉所有狀態並進行狀態轉移來計算\(P(f(S) = x)\),時間複雜度為\(1024\times n\)。
我們定義\(f_{i,x}\)為在前\(i\)個元素中,\(S\)異或和為\(x\)的機率。
易推得狀態轉移方程
由於\(f_i\)只能由\(f_{i-1}\)轉移過來,所以我們可以壓縮掉一個儲存空間
期望的計算
當\(f[x]\)確定後,期望\(E[(f(S))^2]\)就可以透過以下公式計算:
程式碼實現
const ll MOD = 1e9 + 7;
// 快速冪函式,用於計算模反元素
ll qmi(ll x, ll k, ll mod)
{
ll res = 1;
x %= mod;
while (k > 0)
{
if (k & 1)
res = res * x % mod;
x = x * x % mod;
k >>= 1;
}
return res;
}
void solve()
{
ll inv = qmi(10000, MOD - 2, MOD);
int n;
cin >> n;
vi a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
vi p(n);
for (int i = 0; i < n; i++)
{
cin >> p[i];
}
vl f(1024, 0);
f[0] = 1; // 初始狀態,只有 0 的機率為 1
for (int i = 0; i < n; i++)
{
vl g(1024);
for (int x = 0; x < 1024; x++)
{
// 不選當前元素的情況
g[x] = (g[x] + f[x] * (10000 - p[i])) % MOD;
// 選中當前元素的情況
g[x] = (g[x] + f[x ^ a[i]] * p[i]) % MOD;
}
for (int x = 0; x < 1024; x++)
{
g[x] = g[x] * inv % MOD;
}
f = g;
}
ll ans = 0;
for (int x = 0; x < 1024; x++)
{
ll sq = (1LL * x * x) % MOD;
ans = (ans + sq * f[x]) % MOD;
}
cout << ans << "\n";
}