Codeforces Round 988 div3 個人題解(A~G)
Dashboard - Codeforces Round 988 (Div. 3) - Codeforces
火車頭
#include <bits/stdc++.h>
using namespace std;
#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 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" // 重置
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<ll, int> pli;
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<vi> vvi;
typedef vector<vl> vvl;
// 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. Twice
Kinich 醒來迎接新一天的開始。他開啟手機,檢視郵箱,發現一份神秘的禮物。他決定拆開禮物。
Kinich 拆開了一個包含 \(n\) 個整數的陣列 \(a\)。最初,Kinich 的得分是 \(0\)。他將執行以下任意次操作:
選擇兩個索引 \(i\) 和 \(j\) ( \(1 \leq i < j \leq n\) ),使得 \(i\) 和 \(j\) 在任何先前的操作中均未被選擇,並且 \(a_i = a_j\)。然後,將 \(1\) 新增到他的得分中。輸出 Kinich 在執行上述任意次操作後可以獲得的最高分數。
輸入
第一行包含一個整數 \(t\) ( \(1 \leq t \leq 500\) ) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(n\) ( \(1 \leq n \leq 20\) ) — \(a\) 的長度。
每個測試用例的下一行包含 \(n\) 個空格分隔的整數 \(a_1, a_2, \ldots, a_n\) ( \(1 \leq a_i \leq n\) )。
輸出
對於每個測試用例,在新行上輸出可獲得的最高分數。
示例
輸入
5
1
1
2
2 2
2
1 2
4
1 2 3 1
6
1 2 3 1 2 3
輸出
0
1
0
1
3
提示
在第一個和第三個測試用例中,Kinich 不能執行任何操作。
在第二個測試用例中,Kinich 可以執行一次操作,選擇 \(i=1\) 和 \(j=2\)。
在第四個測試用例中,Kinich 可以執行一次操作,選擇 \(i=1\) 和 \(j=4\)。
解題思路
使用map統計一下有多少相同的數對即可。
題解程式碼
void solve()
{
int n;
cin >> n;
map<int, int> mp;
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
mp[x]++;
}
int ans = 0;
for (auto it : mp)
{
ans += it.sd / 2;
}
cout << ans << "\n";
}
B. Intercepted Inputs
為了幫助您為即將到來的 Codeforces 競賽做準備,Citlali 設定了一個網格問題,並試圖透過您的輸入流為您提供一個 \(n \times m\) 的網格。具體來說,您的輸入流應包含以下內容:
第一行包含兩個整數 \(n\) 和 \(m\) — 網格的尺寸。
以下 \(n\) 行分別包含 \(m\) 個整數 — 網格的值。
但是,有人擷取了您的輸入流,打亂了所有給定的整數,並將它們全部放在一行上!現在,一行上有 \(k\) 個整數,而您不知道每個整數原本屬於哪裡。您決定自己確定 \(n\) 和 \(m\) 的值,而不是要求 Citlali 重新傳送輸入。
輸入
第一行包含一個整數 \(t\) ( \(1 \leq t \leq 10^4\) ) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(k\) ( \(3 \leq k \leq 2 \cdot 10^5\) ) — 輸入流中的輸入總數。
每個測試用例的下一行包含 \(k\) 個整數 \(a_1, a_2, \ldots, a_k\) ( \(1 \leq a_i \leq k\) ) — 輸入流的混洗輸入。保證 \(n\) 和 \(m\) 包含在 \(k\) 個整數內。
保證所有測試用例的 \(k\) 之和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,輸出兩個整數,一個可能值為 \(n\) 和 \(m\)。如果存在多個可能的答案,則輸出任意一個。
示例
輸入
5
3
1 1 2
11
3 3 4 5 6 7 8 9 9 10 11
8
8 4 8 3 8 2 8 1
6
2 1 4 5 3 3
8
1 2 6 3 8 5 5 3
輸出
1 1
3 3
2 3
4 1
1 6
提示
在第一個測試用例中,初始輸入可能是:
1 1
2
在第二個測試用例中,初始輸入可能是:
3 3
4 5 6
7 8 9
9 10 11
解題思路
列舉所有 \(a_i\) 查詢是否存在 \(a_j\) 使得 \(a_i\times a_j = k-2\) 即可。
題解程式碼
void solve()
{
int k;
cin >> k;
map<int, int> mp;
for (int i = 1; i <= k; i++)
{
int x;
cin >> x;
mp[x]++;
}
k -= 2;
for (auto it : mp)
{
if (k % it.ft == 0 && mp[k / it.ft])
{
cout << it.ft << " " << k / it.ft << "\n";
return;
}
}
}
C. Superultra's Favorite Permutation
小熊貓 Superultra 非常想要原石。在夢中,一個聲音告訴他,他必須解決以下任務才能獲得一生所需的原石。幫助 Superultra!
構造一個長度為 \(n\) 的排列 \(p\),使得 \(p_i + p_{i+1}\) 是整個 \(1 \leq i \leq n-1\) 的複合數。如果不可能,則輸出 \(-1\)。
一個長度為 \(n\) 的排列是一個由 \(n\) 個不同整陣列成的陣列,這些整數從 \(1\) 到 \(n\) 以任意順序排列。例如, \([2, 3, 1, 5, 4]\) 是排列,但 \([1, 2, 2]\) 不是排列(\(2\) 在陣列中出現兩次),\([1, 3, 4]\) 也不是排列(\(n=3\) 但陣列中有 \(4\))。
如果整數 \(x\) 除了 \(1\) 和 \(x\) 之外至少還有一個其他除數,則該整數為合數。例如,\(4\) 為合數,因為 \(2\) 是除數。
輸入
第一行包含 \(t\) ( \(1 \leq t \leq 10^4\) ) — 測試用例的數量。
每個測試用例包含一個整數 \(n\) ( \(2 \leq n \leq 2 \cdot 10^5\) ) — 排列的長度。
保證所有測試用例的 \(n\) 之和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,如果無法構造 \(p\),則在新行上輸出 \(-1\)。否則,在新行上輸出 \(n\) 個整數 \(p_1, p_2, \ldots, p_n\)。
示例
輸入
2
3
8
輸出
-1
1 8 7 3 6 2 4 5
解題思路
對於相鄰兩個數,如果它們奇偶性相同,則相加一定為偶數,所以我們可以把所有奇數放前面,偶數放後面,只需要考慮奇數和偶數相加的情況即可。發現對於 \(n\ge8\) 的情況,我們只需要把 \(1\) 和 \(8\) 放在一起即可,對於 \(n\lt 8\) 我們寫個程式暴力打表計算即可。
打表程式:
#include <bits/stdc++.h>
using namespace std;
bool isPrime(int x)
{
for (int i = 2; i * i <= x; i++)
{
if(x % i==0)
return false;
}
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
vector<int> a ;
for (int i = 1; i <= 7; i++)
{
a.push_back(i);
bool flag = true;
do {
flag = true;
for (int i = 0; i < a.size() - 1; i++)
{
if (isPrime(a[i] + a[i + 1]))
{
flag = false;
break;
}
}
if (flag)
{
for (auto x : a)
{
cout << x << " ";
}
cout << "\n";
break;
}
} while (next_permutation(a.begin(), a.end()));
if (!flag)
cout << "-1\n";
}
return 0;
}
題解程式碼
void solve()
{
int n;
cin >> n;
vi ans;
if (n < 5)
{
cout << -1 << "\n";
return;
}
else if (n == 7)
{
cout << "1 3 5 4 6 2 7\n";
return;
}
else if (n == 6)
{
cout << "1 3 5 4 2 6\n";
return;
}
else if (n == 5)
{
cout << "1 3 5 4 2\n";
return;
}
for (int i = 3; i <= n; i += 2)
{
ans.pb(i);
}
ans.pb(1);
ans.pb(8);
for (int i = 2; i <= n; i += 2)
{
if (i == 8)
continue;
ans.pb(i);
}
for (int i = 0; i < n; i++)
{
cout << ans[i] << " \n"[i == n - 1];
}
}
D. Sharky Surfing
Mualani 喜歡在她的鯊魚衝浪板上衝浪!
Mualani 的衝浪路徑可以用數字線建模。她從位置 \(1\) 開始,路徑在位置 \(L\) 結束。當她處於位置 \(x\) 且跳躍能力為 \(k\) 時,她可以跳到區間 \([x,x+k]\) 中的任何位置。最初,她的跳躍能力為 \(1\)。
但是,她的衝浪路徑並不完全平坦。她的路徑上有 \(n\) 個障礙。每個障礙都由一個區間 \([l,r]\) 表示,這意味著她無法跳到區間 \([l,r]\) 中的任何位置。
路徑上的某些位置還有 \(m\) 個強化道具。道具 \(i\) 位於位置 \(x_i\),其值為 \(v_i\)。當 Mualani 位於位置 \(x_i\) 時,她可以選擇收集道具,以增加她的跳躍力 \(v_i\)。同一位置可能有多個道具。當她位於具有一些道具的位置時,她可以選擇獲取或忽略每個道具。任何障礙的間隔內均沒有道具。
她必須收集的最少道具數量是多少才能到達位置 \(L\) 並完成路徑?如果無法完成衝浪路徑,則輸出 \(-1\)。
輸入
第一行包含一個整數 \(t\) ( \(1 \leq t \leq 10^4\) ) — 測試用例的數量。
每個測試用例的第一行包含三個整數 \(n\)、\(m\) 和 \(L\) ( \(1 \leq n,m \leq 2 \cdot 10^5, 3 \leq L \leq 10^9\) ) — 障礙數量、強化道具數量和終點位置。
接下來的 \(n\) 行包含兩個整數 \(l_i\) 和 \(r_i\) ( \(2 \leq l_i \leq r_i \leq L-1\) ) — 第 \(i\) 個障礙的區間界限。保證所有 \(1 \leq i < n\) 的 \(r_i + 1 < l_{i+1}\) (即所有障礙都不重疊,按升序排序,並且前一個障礙的終點與下一個障礙的起點不連續)。
以下 \(m\) 行包含兩個整數 \(x_i\) 和 \(v_i\) ( \(1 \leq x_i, v_i \leq L\) ) — 第 \(i\) 個道具的位置和值。可能有多個道具具有相同的 \(x\)。保證所有 \(1 \leq i < m\) 的 \(x_i \leq x_{i+1}\) (即道具按非降序位置排序),並且沒有道具位於任何障礙的間隔內。
保證所有測試用例的 \(n\) 之和與 \(m\) 之和不超過 \(2 \cdot 10^5\)。
輸出
對於每個測試用例,輸出她必須收集的最少能量提升數量才能到達位置 \(L\)。如果不可能,則輸出 \(-1\)。
示例
輸入
4
2 5 50
7 14
30 40
2 2
3 1
3 5
18 2
22 32
4 3 50
4 6
15 18
20 26
34 38
1 2
8 2
10 2
1 4 17
10 14
1 6
1 2
1 2
16 9
1 2 10
5 9
2 3
2 2
輸出
4
-1
1
2
提示
在第一個測試用例中,她可以收集道具 \(1\)、\(2\)、\(3\) 和 \(5\) 來清除所有障礙。
在第二個測試用例中,她無法跳過第一個障礙。
在第四個測試用例中,透過收集兩個道具,她可以跳過障礙。
解題思路
對於每一次拿去,我們肯定是貪心的拿取可以拿但還未拿的道具中對能力提升最大的那一個。所以我們可以使用堆來維護道具。列舉沒一個障礙,將這個障礙之前的所有道具加入堆中,然後從堆中拿取道具直到可以跳過障礙。
題解程式碼
#define l first
#define r second
void solve()
{
int n, m, L;
cin >> n >> m >> L;
vector<pii> a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i].l >> a[i].r;
}
vector<pii> b(m + 1);
int ans = -1;
for (int i = 1; i <= m; i++)
{
cin >> b[i].ft >> b[i].sd;
}
ll val = 1;
int cnt = 0;
int j = 1;
priority_queue<int> q;
for (int i = 1; i <= n; i++)
{
while (j <= m && b[j].ft < a[i].l)
{
q.push(b[j].sd);
j++;
}
while (q.size() && val < a[i].r - a[i].l + 2)
{
val += q.top();
cnt++;
q.pop();
}
if (val < a[i].r - a[i].l + 2)
{
cout << "-1\n";
return;
}
}
cout << cnt << "\n";
}
E. Kachina's Favorite Binary String
這是一個互動式問題。
Kachina 挑戰您猜出她最喜歡的長度為 \(n\) 的二進位制字串 \(s\)。她將 \(f(l,r)\) 定義為 \(s_l s_{l+1} \ldots s_r\) 中 \(01\) 的子序列數。如果兩個子序列是透過從原始字串的不同位置刪除字元形成的,則它們被視為不同,即使生成的子序列由相同的字元組成。
要確定 \(s\),您可以問她一些問題。在每個問題中,您可以選擇兩個索引 \(l\) 和 \(r\) ( \(1 \leq l < r \leq n\) ),並詢問她 \(f(l,r)\) 的值。
在向 Kachina 詢問不超過 \(n\) 個問題後,確定並輸出 \(s\)。但是,可能存在無法確定 \(s\) 的情況。在這種情況下,您需要改為報告 IMPOSSIBLE。
正式來說,如果在詢問 \(n\) 個問題後,無論詢問什麼問題,對於 \(s\) 總是存在多個可能的字串,則無法確定 \(s\)。請注意,如果您報告 IMPOSSIBLE 當存在最多 \(n\) 個查詢序列可以唯一地確定二進位制字串時,您將得到錯誤答案的判定。
輸入
輸入的第一行包含一個整數 \(t\) ( \(1 \leq t \leq 10^3\) ) — 測試用例的數量。
每個測試用例的第一行包含一個整數 \(n\) ( \(2 \leq n \leq 10^4\) ) — \(s\) 的長度。
保證所有測試用例的 \(n\) 的總和不超過 \(10^4\)。
互動
要提出問題,請按以下格式輸出一行(不包括引號)
"? l r"
陪審團將返回一個整數 \(f(l,r)\)。
當您準備列印答案時,請按以下格式輸出一行:
如果無法確定 \(s\),則輸出 "! IMPOSSIBLE"
否則,輸出 "! s"
之後,繼續處理下一個測試用例,如果這是最後一個測試用例,則終止程式。列印答案不算作查詢。
互動器不自適應,這意味著答案在參與者提出查詢之前就已經知道,並且不依賴於參與者提出的問題。
如果您的程式對一個測試用例進行了超過 \(n\) 次查詢,則程式應立即終止以接收判定“錯誤答案”。否則,您可能會得到任意判定,因為您的解決方案將繼續從封閉的流中讀取。
列印查詢後,不要忘記輸出行尾並重新整理輸出。否則,您可能會得到“空閒限制超出”的判定。為此,請使用:
- C++ 中的
fflush(stdout)
或cout.flush()
; - Java 中的
System.out.flush()
; - Pascal 中的
flush(output)
; - Python 中的
stdout.flush()
。
示例
輸入
2
5
4
輸出
? 1 5
? 2 4
? 4 5
? 3 5
! 01001
? 1 2
! IMPOSSIBLE
提示
在第一個測試用例中,您可以透過詢問 Kachina 關於不同區間的 \(f(l,r)\) 值來確定字串。
在第二個測試用例中,由於只有一個可能的查詢,無法區分字串 \(00\) 和 \(11\),因此報告 IMPOSSIBLE。
解題思路
如果字串不存在 \(01\) 子序列,我們查詢什麼都是 \(0\) ,因此一定無法確定字串。
對於字元 \(s\) ,我們可以從 \(1\) 開始列舉所有的 \(i\) 和 \(i+1\) 直到找到 第一個 \(01\) 子字串的位置 \(p\),然後我們透過詢問 \([1,p]\) 即可知道 \([1,p]\) 中有多少個 \(0\) ,把這些零放在最後面,剩下的放 \(1\) 就得到了符合條件的 \([1,p]\) 的子字串。
然後我們再從 \(p+1\) 開始列舉,查詢 \([1,i]\) ,如果查詢的 \(01\) 序列數增多了,說明該位置為 \(1\) 否則 為 \(0\) 。
題解程式碼
const string noFound = "IMPOSSIBLE";
int query(int l, int r)
{
printf("? %d %d\n", l, r);
fflush(stdout);
return read<int>();
}
void answer(string x)
{
printf("! %s\n", x.c_str());
fflush(stdout);
}
void solve()
{
int n = read<int>();
int p = 0;
for (int i = 2; i <= n; i++)
{
if (query(i - 1, i))
{
p = i;
break;
}
}
if (!p)
{
answer(noFound);
return;
}
int now = query(1, p);
string ans(n + 1, '0');
for (int i = 1; i < p - now; i++)
{
ans[i] = '1';
}
ans[p] = '1';
for (int i = p + 1; i <= n; i++)
{
int temp = query(1, i);
if (temp > now)
ans[i] = '1';
now = temp;
}
answer(ans.substr(1));
}
F. Ardent Flames
您已獲得新的限時活動角色 Xilonen。您決定在戰鬥中使用她。
一行中有 \(n\) 個敵人。左側第 \(i\) 個敵人的生命值為 \(h_i\),當前位置為 \(x_i\)。Xilonen 的攻擊傷害為 \(m\),您已準備好用她擊敗敵人。
Xilonen 具有強大的“地面踐踏”攻擊。在執行任何攻擊之前,您選擇一個整數 \(p\) 並將 Xilonen 定位在那裡(\(p\) 可以是任何整數位置,包括當前有敵人的位置)。之後,每次攻擊時,她都會對位置 \(p\) 處的敵人造成 \(m\) 傷害(如果有),對位置 \(p-1\) 和 \(p+1\) 處的敵人造成 \(m-1\) 傷害,對位置 \(p-2\) 和 \(p+2\) 處的敵人造成 \(m-2\) 傷害,依此類推。距離 Xilonen 至少為 \(m\) 的敵人不會受到任何傷害。
正式來說,如果位置 \(x\) 處有敵人,她每次擊中都會對該敵人造成 \(\max(0,m-|p-x|)\) 傷害。請注意,您不能為不同的攻擊選擇不同的 \(p\)。
在所有可能的 \(p\) 中,輸出 Xilonen 必須執行的最少攻擊次數,以擊敗至少 \(k\) 個敵人。如果無法找到 \(p\),使得最終至少有 \(k\) 個敵人會被擊敗,則輸出 \(-1\)。請注意,如果敵人的生命值達到 \(0\) 或以下,則視為被擊敗。
輸入
第一行包含一個整數 \(t\) ( \(1 \leq t \leq 10^4\) ) - 測試用例的數量。
每個測試用例的第一行包含三個整數 \(n\)、\(m\) 和 \(k\) ( \(1 \leq k \leq n \leq 10^5\) 、 \(1 \leq m \leq 10^9\) )。
下一行包含 \(n\) 個整數 \(h_1,h_2,\ldots,h_n\) ( \(1 \leq h_i \leq 10^9\) )。
每個測試用例的最後一行包含 \(n\) 個整數 \(x_1,x_2,\ldots,x_n\) ( 對於所有 \(1 \leq i < n\) ,則為 \(1 \leq x_i \leq 10^9\) , \(x_i < x_{i+1}\) )。
保證所有測試用例的 \(n\) 的總和不超過 \(10^5\)。
輸出
對於每個測試用例,在新行上輸出一個整數,即擊敗至少 \(k\) 個敵人所必須執行的最少攻擊次數。如果無法找到 \(p\),使得最終至少 \(k\) 個敵人將被擊敗,則輸出 \(-1\)。
示例
輸入
6
5 5 3
7 7 7 7 7
1 2 3 4 5
9 5 9
2 4 6 8 10 8 6 4 2
1 2 3 4 5 6 7 8 9
2 10 2
1 1
1 20
2 10 1
69696969 420420420
1 20
2 10 2
10 15
1 19
2 2 2
1000000000 1
1 3
輸出
2
2
-1
6969697
15
1000000000
提示
在第一個測試用例中,選擇 \(p=2\) 是最優的。每次攻擊,第一個敵人受到 \(5-|2-1|=4\) 傷害,第二個敵人受到 \(5\) 傷害,第三個敵人受到 \(4\) 傷害,第四個敵人受到 \(3\) 傷害,第五個敵人受到 \(2\) 傷害。經過 \(2\) 次攻擊,前 \(3\) 個敵人將被擊敗。可以證明,無論選擇哪個 \(p\),都無法在少於 \(2\) 次攻擊中擊敗 \(3\) 個敵人。
在第二個測試用例中,我們必須擊敗所有 \(9\) 個敵人。選擇 \(p=5\),所有九個敵人將在 \(2\) 次攻擊中被擊敗。
在第三個測試用例中,我們必須擊敗兩個敵人。然而,可以證明沒有選擇的 \(p\) 能同時對兩個敵人造成傷害,因此答案是 \(-1\)。
在第四個測試用例中,選擇 \(p=1\) 將使我們在 \(6969697\) 次攻擊中擊敗第一個敵人。
在第五個測試用例中,選擇 \(p=10\) 將使每個敵人每次攻擊受到 \(1\) 點傷害。兩個敵人將在 \(15\) 次攻擊中被擊敗。
解題思路
觀察發現,這是一道求最小可行解的問題,具有二段性,考慮二分。
我們可以二分最小的攻擊次數 \(q\),使得存在一個位置 \(p\) ,使得至少 \(k\) 個敵人在 \(q\) 次攻擊後被擊敗。
設計檢查函式。
對於給定的攻擊次數 \(q\) ,我們需要檢查是否存在一個位置 \(p\) ,使得至少 \(k\) 個敵人被擊敗。
對於每個敵人,計算其被擊敗所需的最小每次攻擊傷害 \(t = \lceil \frac {h_i}{q} \rceil )\) ,如果 \(m \ge t\),則該敵人可以在 \(q\) 次攻擊內被擊敗。
對於每個可被擊敗的敵人,計算需要位於的位置範圍 \(p\in[l, r]\),使得 \(|p - x_i| \le m - t\) 。
將所有這些位置範圍轉化為區間事件,使用掃描線演算法檢查是否存在一個位置 \(p\) 被至少 \(k\) 個區間覆蓋。
題解程式碼
void solve()
{
ll n, m, k;
cin >> n >> m >> k;
vl h(n);
for (int i = 0; i < n; i++)
{
cin >> h[i];
}
vl x(n);
for (int i = 0; i < n; i++)
{
cin >> x[i];
}
ll l = 1;
ll r = *max_element(all(h));
ll ans = -1;
auto check = [&](ll mid)
{
vector<pll> v;
for (int i = 0; i < n; ++i)
{
ll t = (h[i] + mid - 1) / mid;
if (m >= t)
{
ll re = m - t;
ll l = x[i] - re;
ll r = x[i] + re;
v.eb(l, 1);
v.eb(r + 1, -1);
}
}
if (v.empty())
return false;
sort(all(v));
ll sum = 0;
for (auto &[_, t] : v)
{
sum += t;
if (sum >= k)
return true;
}
return false;
};
while (l <= r)
{
ll mid = (l + r) >> 1;
if (check(mid))
{
r = mid - 1;
ans = mid;
}
else
{
l = mid + 1;
}
}
cout << ans << "\n";
}
G. Natlan Exploring
您正在探索令人驚歎的 Natlan 地區!該地區由 \(n\) 個城市組成,每個城市的吸引力評級為 \(a_i\)。當且僅當 \(i < j\) 和 \(\text{gcd}(a_i, a_j) \neq 1\) 時,從城市 \(i\) 到城市 \(j\) 存在有向邊,其中 \(\text{gcd}(x, y)\) 表示整數 \(x\) 和 \(y\) 的最大公約數 (GCD)。
從城市 1 出發,您的任務是確定到達城市 \(n\) 可以採取的不同路徑總數,模數為 \(998244353\)。當且僅當訪問的城市集合不同時,兩條路徑才是不同的。
輸入
第一行包含一個整數 \(n\) ( \(2 \leq n \leq 2 \cdot 10^5\) ) — 城市數量。
第二行包含 \(n\) 個整數 \(a_1, a_2, \ldots, a_n\) ( \(2 \leq a_i \leq 10^6\) ) — 每個城市的吸引力。
輸出
輸出到達城市 \(n\) 可以採取的不同路徑總數,模數為 \(998244353\)。
示例
輸入
5
2 6 3 4 6
輸出
5
輸入
5
4 196 2662 2197 121
輸出
2
輸入
7
3 6 8 9 11 12 20
輸出
7
輸入
2
2 3
輸出
0
提示
在第一個示例中,五條路徑如下:
城市 1 → 城市 5
城市 1 → 城市 2 → 城市 5
城市 1 → 城市 2 → 城市 3 → 城市 5
城市 1 → 城市 2 → 城市 4 → 城市 5
城市 1 → 城市 4 → 城市 5
在第二個示例中,兩條路徑如下:
城市 1 → 城市 3 → 城市 5
城市 1 → 城市 2 → 城市 3 → 城市 5
解題思路
看到計數問題,我們先考慮暴力 \(dp\),我們設 \(f[i]\) 到達 \(i\) 城市的路徑數,易得狀態轉移方程如下:
時間複雜度為 \(O(n^2)\) 由於 \(n\le 100000\),因此肯定超時,考慮最佳化。
觀察發現,對於一個數 \(x=p_{1}^{k_1} \times p_{2}^{k_2} \times \dots \times p_{x}^{k_x}\) 。我們設 \(P = \bigcup_{i=1}^{n} p_i\) ,到達 \(x\) 的路徑實際上可以歸類為$v_S=\prod_{j\in S}j ,
S \subseteq P,S\neq \emptyset \(。\)v_S$ 為集合 \(S\) 的乘積。因為兩個數要互相可達當且僅當不互質,即存在一個相同的質因數,因此對於一個數 \(x\) ,\(y=p_{1}^2\times p_{2}^1\) 和 \(z=p_{1}^3\times p_{2}^4\) 到達它實際上是可以等價的,即\(v_{S_z}=v_{S_y}\) 。因為\(2\times 3\times 5\times 7\times 11\times 13\times 17\times19 =9699690 \gt 1000000\),所以一個數最多隻有 \(7\) 個質因子,它的轉移狀態只有 \(2^7=128\) 種,我們可以極大的簡化每一個數從哪裡轉移過來所需的計算次數,時間複雜度為 \(O(128n)\) 。
如果我們直接加和計算 \(x\) 的轉移狀態,會發現對於從 \(p_1\times p_2\) 轉移和從 \(p_1\times p_2 \times p_3\) 轉移我們會進行重複計算。因此我們需要使用容斥原理來進行計算。對於質因子數為奇數的我們加上,偶數的我們減去,可以發現,這就是莫比烏斯函式(一開始想的是列舉質因子倍數然後調和級數求和算路徑總數,用莫比烏斯函式做容斥,發現時間複雜度會超一點,後來發現直接算多重集容斥即可)。
我們設 \(g[v_S]\) 表示所有之前城市 \(j,j < i\),其 \(a_j\) 的質因數包含子集 \(S\) 中所有質因數的路徑數之和,其中 \(v_S\) 為集合 \(S\) 中所以數的乘積 \(S \subseteq P_i,S\neq \emptyset\) , \(P_i\) 為 \(a_i\) 的質因數的全集。
易得狀態轉移方程:
推得最終的狀態轉移方程
題解程式碼
const int N = 1e6 + 5; // 假設最大範圍為 1000000
int pri[N]; // 儲存所有素數
int minp[N]; // minp[x] 儲存 x 的最小質因子
int cnt; // 記錄素數數量
const int mod = 998244353;
// 尤拉篩函式
void sieve(int n)
{
cnt = 0; // 初始化素數計數器
for (int i = 2; i <= n; i++)
{
if (minp[i] == 0)
{ // 如果 minp[i] 為 0,說明 i 是素數
minp[i] = i; // 記錄 i 的最小質因子為自身
pri[cnt++] = i; // 儲存素數
}
// 利用已找到的素數進行篩選
for (int j = 0; j < cnt && pri[j] <= n / i; j++)
{
minp[pri[j] * i] = pri[j]; // 篩掉 i * primes[j],並記錄最小質因子
if (i % pri[j] == 0)
break; // 防止重複篩選
}
}
}
vi func(int x)
{
vi f;
while (x > 1)
{
int p = minp[x];
f.eb(p);
while (x % p == 0)
x /= p;
}
return f;
}
void solve()
{
int n;
cin >> n;
vi a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
int maxA = *max_element(all(a));
sieve(maxA);
vl f(n + 1);
f[1] = 1;
vl g(maxA + 1);
vi primes = func(a[0]);
int m = primes.size();
for (int mask = 0; mask < (1 << m); mask++)
{
ll d = 1;
for (int b = 0; b < m; b++)
{
if (mask & (1 << b))
d *= primes[b];
}
if (d > maxA)
continue;
g[d] = (g[d] + f[1]) % mod;
}
ll tot = f[1];
for (int i = 2; i <= n; i++)
{
vi p = func(a[i - 1]);
int num = p.size();
ll sum = 0;
for (int mask = 0; mask < (1 << num); mask++)
{
ll d = 1;
bool flag = true;
for (int b = 0; b < num; b++)
{
if (mask & (1 << b))
{
d *= p[b];
if (d > maxA)
{
flag = false;
break;
}
}
}
if (!flag)
continue;
int mu = (__builtin_popcount(mask) % 2 == 0) ? -1 : 1;
sum = (sum + mu * g[d] + mod) % mod;
}
f[i] = (tot + sum + mod) % mod;
for (int mask = 0; mask < (1 << num); mask++)
{
ll d = 1;
for (int b = 0; b < num; b++)
{
if (mask & (1 << b))
d *= p[b];
}
if (d > maxA)
continue;
g[d] = (g[d] + f[i]) % mod;
}
tot = (tot + f[i]) % mod;
}
cout << f[n] << "\n";
}
這場超神發揮,首次d3ak,不過感覺這場d3也比以往的更簡單。