Codeforces Rund 977 div2 個人題解(A~E1)

ExtractStars發表於2024-10-06

Codeforces Rund 977 div2 個人題解(A,B,C1,C2,E1)

Dashboard - Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round) - 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. Meaning Mean

每個測試的時間限制:1秒

每個測試的記憶體限制:256兆位元組

輸入:標準輸入

輸出:標準輸出

Pak Chanek 有一個長度為 n 的正整數陣列 a。因為他正在學習如何計算兩個數字的取整平均值,所以他想在他的陣列 a 上練習這個技能。

當陣列 a 至少有兩個元素時,Pak Chanek 將執行以下三步操作:

  1. 選擇兩個不同的索引 ij1 \leq i, j \leq |a|; i \neq j),注意 |a| 表示陣列 a 的當前大小。

  2. \lfloor \frac{a_i + a_j}{2} \rfloor 新增到陣列的末尾。

  3. 從陣列中移除元素 a_ia_j,並連線剩下的部分。

例如,假設 a = [5, 4, 3, 2, 1, 1]。如果我們選擇 i=1j=5,則結果陣列將是 a = [4, 3, 2, 1, 3]。如果我們選擇 i=4j=3,則結果陣列將是 a = [5, 4, 1, 1, 2]

在所有操作完成後,陣列將只包含一個元素 x。如果 Pak Chanek 進行最佳操作,找出 x 的最大可能值。

輸入

每個測試包含多個測試用例。第一行包含測試用例的數量 t1 \leq t \leq 5000)。每個測試用例的描述如下。

每個測試用例的第一行包含一個整數 n2 \leq n \leq 50)——陣列 a 的長度。

每個測試用例的第二行包含 n 個整數 a_1, a_2, \ldots, a_n1 \leq a_i \leq 10^9)——陣列 a 的元素。

請注意,所有測試用例的 n 的總和沒有上限。

輸出

對於每個測試用例,輸出一個整數:所有數字被選出後 x 的最大可能值。

示例

輸入

3
5
1 7 8 4 5
3
2 6 5
5
5 5 5 5 5

輸出

6
4
5

注意

在第一個測試用例中,初始陣列為 a=[1,7,8,4,5]。Pak Chanek 將執行以下操作:

  1. 選擇 i=1j=2,然後 a=[8,4,5,4]

  2. 選擇 i=3j=2,然後 a=[8,4,4]

  3. 選擇 i=2j=3,然後 a=[8,4]

  4. 選擇 i=1j=2,然後 a=[6]

在所有操作完成後,陣列只包含一個元素 x=6。可以證明沒有一系列操作會導致 x 超過 6

解題思路

注意到,每一次操作都讓之前所有加入的數對最後答案的貢獻減半,第一次加入的數會減半n-1次,

第二次加入的數會減半n-2次,以此類推,越晚加入的數減半的次數越少。

所以我們只要對陣列進行排序,從小到大進行操作即可。

程式碼實現

void solve()
{
int n;
cin >> n;
vi a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
sort_all(a);
int now = a[0];
for (int i = 1; i < n; i++)
{
now = (now + a[i]) / 2;
}
cout << now << endl;
}

B. Maximize Mex

每個測試的時間限制:1秒

每個測試的記憶體限制:256兆位元組

輸入:標準輸入

輸出:標準輸出

你有一個長度為 n 的正整數陣列 a 和一個整數 x。你可以進行以下兩個步驟的操作任意(可能為零)次數:

  1. 選擇一個索引 i (1 \leq i \leq n)。

  2. a_i 增加 x,也就是說 a_i := a_i + x

如果你最佳化地執行這些操作,求陣列 a\operatorname{MEX} 的最大值。

\operatorname{MEX}(最小缺失值)是陣列中最小的非負整數且不在陣列中的值。例如:

  • [2,2,1]\operatorname{MEX}0,因為 0 不在陣列中。

  • [3,1,0,1]\operatorname{MEX}2,因為 01 都在陣列中,但 2 不在。

  • [0,3,1,2]\operatorname{MEX}4,因為 0, 1, 23 都在陣列中,但 4 不在。

輸入

每個測試包含多個測試用例。第一行包含測試用例的數量 t (1 \le t \le 5000)。每個測試用例的描述如下。

每個測試用例的第一行包含兩個整數 nx (1 \le n \le 2 \cdot 10^5; 1 \le x \le 10^9) — 陣列的長度和用於操作的整數。

每個測試用例的第二行包含 n 個整數 a_1, a_2, \ldots, a_n (0 \le a_i \le 10^9) — 給定的陣列。

保證所有測試用例中 n 的總和不超過 2 \cdot 10^5

輸出

對於每個測試用例,輸出一個整數:如果你最佳化地執行操作,陣列 a\operatorname{MEX} 的最大值。

示例

輸入

3
6 3
0 3 2 1 5 2
6 2
1 3 4 1 0 2
4 5
2 5 10 3

輸出

4
6
0

注意

在第一個測試用例中,陣列 a\operatorname{MEX}4,無需執行任何操作,這是最大的可能值。

在第二個測試用例中,陣列 a\operatorname{MEX}5,無需執行任何操作。如果我們執行兩次操作,都選擇 i=1,則陣列將變為 a=[5,3,4,1,0,2]。然後,陣列 a\operatorname{MEX} 將變為 6,這是最大的可能值。

在第三個測試用例中,陣列 a\operatorname{MEX}0,無需執行任何操作,這是最大的可能值。

解題思路

觀察發現,對於每一個數字a_i而言,我們都只能將它變成k_i \cdot x+a_i\mod d,k_i \ge \frac{a_i}{x}的形式。所以我們可以對a_i按照a_i \mod x進行分類存進map中。

列舉mex,並計算k=\frac {mex}{x}r=mex \mod x。查詢map[r]中是否有至少k個數小於等於mex,是的話mex+1,否則不再列舉。

查詢是否有k個數小於mex可以直接對map[r]中的數進行排序

時間複雜度為O(nlogn)

程式碼實現

void solve()
{
ll n, x;
cin >> n >> x;
vl a(n);
map<ll, vl> mp;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
sort_all(a);
for (int i = 0; i < n; i++)
{
mp[a[i] % x].pb(a[i]);
}
ll mex = 0;
while (true)
{
ll r = mex % x;
ll k = mex / x;

if (mp[r].size() > k && mp[r][k] <= r + k * x)
mex++;
else
break;
}

cout << mex << "\n";
}

C1. Adjust The Presentation (Easy Version)

時間限制:每個測試 2 秒

記憶體限制:每個測試 256 兆位元組

輸入:標準輸入

輸出:標準輸出

這是問題的簡單版本。在兩個版本中,q 的約束和時間限制不同。在此版本中,q=0。只有在解決了所有版本的問題後,您才能進行駭客攻擊。

一個由 n 名成員組成的團隊,編號從 1n,準備在一個大型會議上展示幻燈片。幻燈片包含 m 張幻燈片。

有一個長度為 n 的陣列 a。成員最初按順序排列為 a_1, a_2, \ldots, a_n,從前到後。幻燈片展示將按順序從幻燈片 1 到幻燈片 m 展示。每個部分將由隊伍最前面的成員進行展示。在每張幻燈片展示後,您可以將最前面的成員移動到隊伍中的任意位置(不改變其他成員的順序)。例如,假設成員的隊伍是 [\color{red}{3},1,2,4]。在成員 3 展示當前幻燈片後,您可以將隊伍改為 [\color{red}{3},1,2,4][1,\color{red}{3},2,4][1,2,\color{red}{3},4][1,2,4,\color{red}{3}]

還有一個長度為 m 的陣列 b。如果可以在這些約束下使得成員 b_i 在所有 i1m 的情況下展示幻燈片,那麼該幻燈片展示被認為是好的。

然而,您討厭的老闆希望對陣列 b 進行 q 次更新。在第 i 次更新中,他將選擇一張幻燈片 s_i 和一個成員 t_i,並設定 b_{s_i} := t_i。請注意,這些更新是持久的,即對陣列 b 的更改將在處理未來的更新時生效。

對於陣列 b 的每個狀態(初始狀態和每次 q 次更新之後),判斷幻燈片展示是否良好。

輸入

每個測試包含多個測試用例。第一行包含測試用例的數量 t (1 \le t \le 10^4)。測試用例的描述如下。

每個測試用例的第一行包含三個整數 n, mq (1 \le n, m \le 2 \cdot 10^5; q=0) — 成員的數量、部分的數量和更新的數量。

每個測試用例的第二行包含 n 個整數 a_1,a_2,\ldots,a_n (1 \le a_i \le n) — 從前到後的成員初始順序。保證 1n 的每個整數恰好出現一次在 a 中。

每個測試用例的第三行包含 m 個整數 b_1, b_2, \ldots, b_m (1 \le b_i \le n) — 每個部分應該展示的成員。

保證所有測試用例中 n 的總和和 m 的總和不超過 2 \cdot 10^5

輸出

對於每個測試用例,輸出 q+1 行,對應於陣列 bq+1 個狀態。如果幻燈片展示是好的,輸出 "YA",否則輸出 "TIDAK"。

您可以以任意大小寫輸出答案(大寫或小寫)。例如,字串 "yA"、"Ya"、"ya" 和 "YA" 都將被識別為正面響應。

示例

輸入

3
4 2 0
1 2 3 4
1 1
3 6 0
1 2 3
1 1 2 3 3 2
4 6 0
3 1 4 2
3 1 1 2 3 4

輸出

YA
YA
TIDAK

注意

在第一個測試用例中,您不需要移動成員,因為兩張幻燈片都是由成員 1 展示的,他已經在隊伍前面。

在第二個測試用例中,以下是可能的移動成員的方式,使得演示是好的:

  1. [1,2,3],不移動成員 1

  2. [1,2,3],在成員 3 之後移動成員 1

  3. [2,3,1],在成員 3 之後移動成員 2

  4. [3,2,1],不移動成員 3

  5. [3,2,1],在成員 1 之後移動成員 3

  6. [2,1,3],不移動成員 2

解題思路

對於初始順序中的每一個成員,他在播放序列中第一次出現的位置一定會大於他的上一個成員在播放序列中的位置,所以我們只需要開一個位置陣列,初始化為inf,然後遍歷播放序列儲存每一個成員第一次出現的位置,如果出現降序的數對,直接輸出不可行即可。

程式碼實現

const string t1 = "YA";
const string t2 = "TIDAK";
void solve()
{
int n, m, q;
cin >> n >> m >> q;

vi a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
vi seq(m + 1);
vi pos(n + 1, inf);

for (int i = 1; i <= m; i++)
{
cin >> seq[i];
if (pos[seq[i]] == inf)
pos[seq[i]] = i;
}
for (int i = 2; i <= n; i++)
{
int cur = a[i];
int pre = a[i - 1];
if ((pos[cur] != inf && pos[pre] == inf) || pos[cur] < pos[pre])
{
cout << t2 << "\n";
return;
}
}
cout << t1 << "\n";
}

C2. Adjust The Presentation (Hard Version)

時間限制:每個測試 5 秒

記憶體限制:每個測試 256 兆位元組

輸入:標準輸入

輸出:標準輸出

這是問題的困難版本。在兩個版本中,q 的約束和時間限制不同。在此版本中,0 \leq q \leq 2 \cdot 10^5。只有在解決了所有版本的問題後,您才能進行駭客攻擊。

一個由 n 名成員組成的團隊,編號從 1n,準備在一個大型會議上展示幻燈片。幻燈片包含 m 張幻燈片。

有一個長度為 n 的陣列 a。成員最初按順序排列為 a_1, a_2, \ldots, a_n,從前到後。幻燈片展示將按順序從幻燈片 1 到幻燈片 m 展示。每個部分將由隊伍最前面的成員進行展示。在每張幻燈片展示後,您可以將最前面的成員移動到隊伍中的任意位置(不改變其他成員的順序)。例如,假設成員的隊伍是 [\color{red}{3},1,2,4]。在成員 3 展示當前幻燈片後,您可以將隊伍改為 [\color{red}{3},1,2,4][1,\color{red}{3},2,4][1,2,\color{red}{3},4][1,2,4,\color{red}{3}]

還有一個長度為 m 的陣列 b。如果可以在這些約束下使得成員 b_i 在所有 i1m 的情況下展示幻燈片,那麼該幻燈片展示被認為是好的。

然而,您討厭的老闆希望對陣列 b 進行 q 次更新。在第 i 次更新中,他將選擇一張幻燈片 s_i 和一個成員 t_i,並設定 b_{s_i} := t_i。請注意,這些更新是持久的,即對陣列 b 的更改將在處理未來的更新時生效。

對於陣列 b 的每個狀態(初始狀態和每次 q 次更新之後),判斷幻燈片展示是否良好。

輸入

每個測試包含多個測試用例。第一行包含測試用例的數量 t (1 \le t \le 10^4)。測試用例的描述如下。

每個測試用例的第一行包含三個整數 n, mq (1 \le n, m \le 2 \cdot 10^5; 0 \leq q \leq 2 \cdot 10^5) — 成員的數量、部分的數量和更新的數量。

每個測試用例的第二行包含 n 個整數 a_1,a_2,\ldots,a_n (1 \le a_i \le n) — 從前到後的成員初始順序。保證 1n 的每個整數恰好出現一次在 a 中。

每個測試用例的第三行包含 m 個整數 b_1, b_2, \ldots, b_m (1 \le b_i \le n) — 每個部分應該展示的成員。

接下來的 q 行,每行包含兩個整數 s_it_i (1 \le s_i \le m, 1 \le t_i \le n) — 更新的引數。

保證所有測試用例中 n 的總和、m 的總和以及 q 的總和不超過 2 \cdot 10^5

輸出

對於每個測試用例,輸出 q+1 行,對應於陣列 bq+1 個狀態。如果幻燈片展示是好的,輸出 "YA",否則輸出 "TIDAK"。

您可以以任意大小寫輸出答案(大寫或小寫)。例如,字串 "yA"、"Ya"、"ya" 和 "YA" 都將被識別為正面響應。

示例

輸入:

3
4 2 2
1 2 3 4
1 1
1 2
1 1
3 6 2
1 2 3
1 1 2 3 3 2
3 3
2 2
4 6 2
3 1 4 2
3 1 1 2 3 4
3 4
4 2

輸出:

YA
TIDAK
YA
YA
TIDAK
YA
TIDAK
YA
YA

注意

在第一個測試用例中,您不需要移動成員,因為兩張幻燈片都是由成員 1 展示的,他已經在隊伍前面。之後設定 b_1 := 2,此時第 1 張幻燈片必須由成員 2 展示,但這是不可能的,因為成員 1 將首先展示幻燈片 1。然後設定 b_1 = 1,此時 b 和初始的 b 相同,使得演示可以順利進行。

解題思路

與C1的思路一致,對於每一個成員,他在播放序列中第一次出現的位置一定會大於他的上一個成員在播放序列中的位置,所以我們只需要維護有存在多少對順序是不合法順序,每次操作後對其進行更新,當不合法對數為零的時候即為"YA"。

程式碼實現

const string t1 = "YA";
const string t2 = "TIDAK";

void solve()
{
int n, m, q;
cin >> n >> m >> q;

vi a(n + 1);
vi pos_a(n + 1, 0); // pos_a[x] 儲存成員x在隊伍中的位置
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
pos_a[a[i]] = i;
}

vi b(m + 1);
for (int i = 1; i <= m; ++i)
{
cin >> b[i];
}

// occ[x] 儲存成員x在b陣列中出現的所有幻燈片位置(有序)
vector<set<int>> occ(n + 1, set<int>());
for (int i = 1; i <= m; ++i)
{
occ[b[i]].insert(i);
}

// f[x] 儲存成員x在b陣列中第一次出現的位置,如果x不出現,則設為m+1
vi f(n + 1, m + 1);
for (int x = 1; x <= n; ++x)
{
if (!occ[x].empty())
f[x] = *occ[x].begin();
}

// bad[i] 表示成員a[i]和a[i+1]的展示順序是否不合法(即f[a[i]] > f[a[i+1]])
vector<bool> bad(n, false);
// 記錄不合法的成員對數量
int cnt = 0;
for (int i = 1; i < n; ++i)
{
if (f[a[i]] > f[a[i + 1]])
{
bad[i] = true; // 標記第i對成員順序不合法
cnt++; // 不合法對數量加1
}
}

if (cnt == 0)
cout << t1 << "\n"; // 如果沒有不合法對,輸出"YA"
else
cout << t2 << "\n"; // 否則,輸出"TIDAK"

// 處理每一次更新操作
for (int i = 0; i < q; ++i)
{
int s, t;
cin >> s >> t;

int las = b[s]; // 記錄更新前第s張幻燈片展示的成員
int nex = t; // 更新後要展示的成員

if (las == nex)
{
// 如果更新前後成員相同,展示順序不變
if (cnt == 0)
cout << t1 << "\n";
else
cout << t2 << "\n";
continue;
}

// 從成員las的出現集合中移除位置s
occ[las].erase(s);
if (!occ[las].empty())
f[las] = *occ[las].begin(); // 更新成員las的第一次出現位置
else
f[las] = m + 1; // 如果成員las不再出現在b中,設為m+1

// 將位置s加入成員nex的出現集合
occ[nex].insert(s);
if (s < f[nex])
f[nex] = s; // 如果s小於當前成員nex的第一次出現位置,更新f[nex]
b[s] = nex; // 更新b陣列中第s張幻燈片展示的成員為nex

// 獲取成員las和nex在初始排列中的位置
int pos_las = pos_a[las];
int pos_nex = pos_a[nex];

// temp集合儲存需要檢查的相鄰成員對的位置
set<int> temp;
if (pos_las > 1)
temp.insert(pos_las - 1); // 檢查pos_las-1和pos_las的成員對
if (pos_las < n)
temp.insert(pos_las); // 檢查pos_las和pos_las+1的成員對
if (pos_nex > 1)
temp.insert(pos_nex - 1); // 檢查pos_nex-1和pos_nex的成員對
if (pos_nex < n)
temp.insert(pos_nex); // 檢查pos_nex和pos_nex+1的成員對

// 遍歷所有需要檢查的相鄰成員對,更新不合法對的計數
for (auto idx : temp)
{
int u = a[idx]; // 成員u在位置idx
int v = a[idx + 1]; // 成員v在位置idx+1
bool was_bad = bad[idx]; // 記錄之前是否不合法
bool is_bad = f[u] > f[v]; // 計算當前是否不合法

if (was_bad != is_bad)
{
if (is_bad)
cnt++; // 如果現在變為不合法,對數加1
else
cnt--; // 如果現在變為合法,對數減1
bad[idx] = is_bad; // 更新bad陣列中的狀態
}
}

// 根據更新後的不合法對數量,輸出結果
if (cnt == 0)
cout << t1 << "\n";
else
cout << t2 << "\n";
}
}

D. Boss, Thirsty

時間限制:每個測試 2 秒

記憶體限制:每個測試 256 兆位元組

輸入:標準輸入

輸出:標準輸出

Pak Chanek 的朋友在食堂經營飲料攤,計劃在接下來的 n 天內售賣飲料。每一天,有 m 種不同型別的飲料售賣,並且第 i 天售賣第 j 種飲料的利潤為 A_{i,j}。這個利潤可能為負數,這意味著賣這種飲料可能會造成虧損。

為了幫助朋友最大化利潤,Pak Chanek 每天必須選擇售賣 至少一種 型別的飲料。售賣的飲料型別必須構成一個子陣列,即每天選擇一個區間 [i, j] 使得 1 \leq i \leq j \leq m,在這個區間內所有型別的飲料都會被售賣。

此外,為了保證每天的顧客持續迴流,銷售計劃必須滿足以下條件:

  • i 天(i > 1)售賣的飲料型別中,至少有一種型別在第 i-1 天也有售賣。

  • i 天售賣的飲料型別中,至少有一種型別在第 i-1 天沒有售賣。

每天的利潤是售賣飲料的總利潤,目標是找到一個銷售計劃,使得在 n 天的銷售計劃中,總利潤最大化。

輸入

每個測試包含多組測試用例。

第一行是測試用例的數量 t (1 \leq t \leq 1000)。

每個測試用例的第一行包含兩個整數 nm (1 \leq n \leq 2 \cdot 10^5; 3 \leq m \leq 2 \cdot 10^5; 且 n \cdot m \leq 2 \cdot 10^5),分別表示天數和飲料的種類數。

接下來的 n 行中,每行包含 m 個整數 A_{i,1}, A_{i,2}, \ldots, A_{i,m} (-10^9 \leq A_{i,j} \leq 10^9),表示每種飲料在當天的預期利潤。

保證所有測試用例中 n \cdot m 的總和不超過 2 \cdot 10^5

輸出

對於每個測試用例,輸出一個整數,表示 Pak Chanek 可以獲得的最大總利潤。

示例

輸入

1
3 6
79 20 49 5 -1000 500
-105 9 109 24 -98 -499
14 47 12 39 23 50

輸出

475

解釋

Note

對於給定的例子,Pak Chanek 的最佳銷售計劃是:

Codeforces Rund 977 div2 個人題解(A~E1)Codeforces Rund 977 div2 個人題解(A~E1)Codeforces Rund 977 div2 個人題解(A~E1)
  • 第 1 天售賣飲料型別 13,總利潤為 79 + 20 + 49 = 148

  • 第 2 天售賣飲料型別 24,總利潤為 9 + 109 + 24 = 142

  • 第 3 天售賣飲料型別 16,總利潤為 185

因此,總利潤為 148 + 142 + 185 = 475

解題思路

使用兩個動態規劃陣列 dpl和 dpr,分別表示當前天選擇的區間以某個位置結尾或開始時的最大利潤。

對於第一天,直接計算每個可能區間的利潤並初始化 dpl 和 dpr。

計算出字首最小字首和pmn和字尾最小字尾和smx,透過前一天的 dpl 和 dpr,結合當前天的 pre、pmn 和 smx,更新當前天的 dpl 和 dpr。

更新 dpl[j]

  • 遍歷從右到左,考慮如果當前天的區間以 j 結尾,可以從前一天的某個區間轉移過來。

  • 更新 ndpl[j] 為前一天的 dpr[j] + smx[j + 1] 與當前天的 pre[j] 之間的差值。

更新 dpr[j]

  • 遍歷從左到右,考慮如果當前天的區間以 j 開始,可以從前一天的某個區間轉移過來。

  • 更新 ndpr[j] 為前一天的 dpl[j] - pmn[j - 1] 與當前天的 pre[j + 1] 之間的和。

在處理完所有天數後,遍歷最後一天的 dpldpr,找到其中的最大值,即為最大總利潤。

看哥哥的提交看的思路,有空補吧


E1. Digital Village (Easy Version)

時間限制:每個測試 2 秒

記憶體限制:每個測試 256 兆位元組

輸入:標準輸入

輸出:標準輸出

這是問題的簡單版本。在三個版本中,nm 的約束不同。只有在解決了所有版本的問題後,您才能進行駭客攻擊。

Pak Chanek 正在為 Khuntien 村莊建立網際網路連線。村莊可以表示為一個有 n 座房子和 m 條網路電纜的連線圖,每條電纜連線房子 u_i 和房子 v_i,延遲為 w_i

p 座房子需要網際網路。Pak Chanek 可以在最多 k 座房子中安裝伺服器。需要網際網路的房子將連線到其中一臺伺服器。但是,由於每條電纜都有其延遲,某個需要網際網路的房子 s_i 體驗到的延遲是其與所連線伺服器之間的電纜的 最大 延遲。

對於每個 k = 1,2,\ldots,n,幫助 Pak Chanek 確定所有需要網際網路的房子可以達到的最小 延遲。

輸入

每個測試包含多個測試用例。第一行包含測試用例的數量 t (1 \le t \le 100)。測試用例的描述如下。

每個測試用例的第一行包含三個整數 n, m, p (2 \le n \le 400; n-1 \le m \le 400; 1 \le p \le n) — 房子的數量、電纜的數量和需要網際網路的房子的數量。

每個測試用例的第二行包含 p 個整數 s_1, s_2, \ldots, s_p (1 \le s_i \le n) — 需要網際網路的房子。保證 s 中所有元素互不相同。

接下來每個測試用例的 m 行中的第 i 行包含三個整數 u_i, v_iw_i (1 \le u_i < v_i \le n; 1 \le w_i \le 10^9) — 連線房子 u_i 和房子 v_i 的網路電纜,延遲為 w_i。保證給定的邊形成一個連通的簡單圖。

保證所有測試用例中 n^3m^3 的總和不超過 10^8

輸出

對於每個測試用例,輸出 n 個整數:對於每個 k = 1,2,\ldots,n,所有需要網際網路的房子可以達到的最小總延遲。

示例

輸入

2
9 8 5
2 5 6 8 9
1 2 1
1 3 2
3 4 10
4 5 3
4 6 5
1 7 10
7 8 4
7 9 2
3 3 2
3 1
1 2 1
2 3 3
1 3 2

輸出

34 19 9 4 0 0 0 0 0
2 0 0

解釋

在第一個測試用例中,對於 k=3 時,可能的最優解決方案是在頂點 268 處安裝伺服器,得到以下延遲:

  • \text{latency}(2) = 0

  • \text{latency}(5) = \max(3, 5) = 5

  • \text{latency}(6) = 0

  • \text{latency}(8) = 0

  • \text{latency}(9) = \max(2, 4) = 4

所以總延遲為 9

解題思路

觀察發現n\le400,可以使用n^3級別的演算法,於是我們可以使用floyd先計算出所有房屋直接的最小延遲,這樣我們就得到了所有需要聯網的房屋與其它房屋之間的最小延遲。

由於n很小,我們可以暴力列舉每個還未放置伺服器的位置,每次新增伺服器選加了伺服器之後總延遲最小的那個。

程式碼實現

void solve()
{
int n, m, p;
cin >> n >> m >> p;
vi tag(p);
for (int i = 0; i < p; i++)
{
cin >> tag[i];
tag[i]--;
}

vvi dist(n, vi(n, inf));
for (int i = 0; i < n; i++)
{
dist[i][i] = 0;
}

for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
u--;
v--;
dist[u][v] = min(dist[u][v], w);
dist[v][u] = min(dist[v][u], w);
}
// Floyd計算所有點對之間的最小延遲
for (int k = 0; k < n; k++)
{
for (int i = 0; i < n; i++)
{
if (dist[i][k] == inf)
continue;
for (int j = 0; j < n; j++)
{
if (dist[k][j] == inf)
continue;
int d = max(dist[i][k], dist[k][j]);
if (d < dist[i][j])
dist[i][j] = d;
}
}
}

// mn[i][j] 表示第i個需要聯網的房子到第j個房子的最小最大延遲
vvi mn(p, vi(n, inf));
// 計算所有要聯網的房子到其它房子的最小延遲
for (int i = 0; i < p; i++)
{
int s = tag[i];
for (int j = 0; j < n; j++)
{
mn[i][j] = dist[s][j];
}
}

// 初始化每個需要聯網的房子的當前延遲為無窮大
vl cur(p, inf);
// select[j] 表示是否選擇了第j個房子作為伺服器
vector<bool> used(n, false);
vl ans;

// 對於k從1到n,選擇k個伺服器
for (int k = 1; k <= n; k++)
{
ll sum = 0; // 當前的總延遲
int idx = -1; // 選擇的伺服器位置

// 尋找未選擇的房子中,選擇一個能最大程度減少總延遲的房子作為伺服器
for (int j = 0; j < n; j++)
{
// 如果已經選擇,跳過
if (used[j])
continue;
// 計算當前伺服器位置到所有需要聯網的房子的最小延遲
ll now = 0;
for (int s = 0; s < p; s++)
{
// 每個需要聯網的房子可以選擇連線到當前伺服器或者之前選擇的伺服器中的最小延遲
now += min((int)cur[s], mn[s][j]);
}
// 選擇能夠使總延遲最小的伺服器位置
if (idx == -1 || now < sum)
{
sum = now;
idx = j;
}
}
used[idx] = true;
// 更新每個需要聯網的房子的當前延遲
for (int s = 0; s < p; s++)
{
cur[s] = min((int)cur[s], mn[s][idx]);
}
// 計算當前k的總延遲
sum = 0;
for (int s = 0; s < p; s++)
{
sum += cur[s];
}
ans.push_back(sum);
// 如果k >= p,後續的k只需要選擇p個伺服器,剩餘選擇不影響,總延遲不再變化
if (k >= p)
{
// 填充剩餘的k到n的結果
while (ans.size() < n)
{
ans.push_back(0);
}
break;
}
}
for (int i = 0; i < n; i++)
{
cout << ans[i] << ' ';
}
cout << '\n';
}

E2. Digital Village (Hard Version)

困難版本中1\le t \le 2000,2 \le n \le 5000,n-1 \le m \le 5000

解題思路

先跑一個Kruskalcg重構樹,得到最小生成樹,然後樹進行樹形dp即可

大體思路是這樣,程式碼後面有時間補吧

相關文章