Codeforces Round #746 (Div. 2) 題解
旅行傳送門
A. Gamer Hemose
題意:給你 \(n\) 種武器,每種武器可以造成 \(a_i\) 的傷害。現在你面對一名生命值為 \(H\) 的敵人,每次攻擊你可以選擇一種武器並對敵人造成相應傷害直至其死亡( \(H \leq 0\) ),但你不能連續兩次都選擇相同的武器。現問你最少需要攻擊多少次可以解決掉敵人?
題目分析:貪心,由於不能連續兩次使用相同的武器,所以輪換著使用傷害值最高的武器和傷害值次高的武器即可。
AC程式碼:
#include <bits/stdc++.h> #define rep(i, x, y) for (register int i = (x); i <= (y); i++) #define down(i, x, y) for (register int i = (x); i >= (y); i--) char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf; #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) inline int read() { int x = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } int solve() { int n, h; n = read(), h = read(); std::vector<int> a(n + 1); rep(i, 1, n) a[i] = read(); std::sort(a.begin() + 1, a.begin() + n + 1); int sum = a[n - 1] + a[n]; int left = h % sum; int res = 2 * (h - left) / sum; while (left > 0) { left -= (res & 1) ? a[n - 1] : a[n]; ++res; } return res; } int main(int argc, char const *argv[]) { int T = read(); while (T--) printf("%d\n", solve()); return 0; }
B. Hemose Shopping
題意:給你一個長度為 \(n\) 的序列,你可以執行下列操作任意次:
- 交換兩個下標分別為 \(i\) 與 \(j\) 的元素 \(a_i\) 與 \(a_j\) ( \(|i - j| \geq x\) )
問是否可能通過有限次操作使得序列按非遞減排序?
題目分析:運用集合的思想來考慮這個問題,對每個 \(i\) ,其與 \([i+x,n]\) 屬於同一集合,我們可以將集合內的元素看成一個新序列,在這個新序列中交換元素不再有上述限制,因此必定能按非遞減排序。不難發現,只有 \([n - x + 1, x]\) 內的元素不在集合中,因此合法的情況只有這些數最開始就待在它們本該待的地方,將它們與排序後的序列一一比對即可。
AC程式碼:
#include <bits/stdc++.h> #define rep(i, x, y) for (register int i = (x); i <= (y); i++) #define down(i, x, y) for (register int i = (x); i >= (y); i--) const int maxn = 1e5 + 5; char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf; #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) inline int read() { int x = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } bool solve() { int n = read(), x = read(); std::vector<int> a(n + 1), b(n + 1); rep(i, 1, n) a[i] = b[i] = read(); if (x <= n / 2) return true; std::stable_sort(b.begin() + 1, b.begin() + n + 1); rep(i, n - x + 1, x) if (a[i] ^ b[i]) return false; return true; } int main(int argc, char const *argv[]) { int T = read(); while (T--) puts(solve() ? "YES" : "NO"); return 0; }
C. Bakry and Partitioning
題意:給你一棵含 \(n\) 個節點,每個節點權值為 \(a_i\) 的樹,現問你能否將這棵樹分為至少 \(2\) 個,至多 \(k\) 個連通塊,且每個塊的異或和都相等。
題目分析:首先求出整棵樹的異或和 \(sum\) ,分類討論:
- \(sum = 0\) ,說明只刪一條邊即可將整棵樹分成兩個異或和相等的連通塊,顯然可行
- \(sum \not= 0\) 且 \(k = 2\) ,假設此時 \(sum = x\) 且 \(x \not= 0\) ,若可將其分成兩個異或和均為 \(y\) 的連通塊,則有 \(y \bigoplus y = x = 0\) ,與假設矛盾,故不成立
- \(sum \not= 0\) 且 \(k > 2\) ,由於異或和的性質,相同的值異或奇數次不變,偶數次為 \(0\) 。因此我們通過 \(dfs\) 每找到一棵異或和為 \(sum\) 的子樹,便將它單獨拆出來,若最後能分成三個以上的連通塊則可行。
用圖解釋就是:
為什麼拆出來的一定是子樹呢?如圖,因為 \(dfs\) 是自下而上搜索的,若拆出來的是一條鏈,說明這條鏈與那個單獨拆出來的子節點的異或和均為 \(sum\) ,但 \(dfs\) 到這個子節點的時候並沒有將它拆出來,說明它的異或和不為 \(sum\) ,與假設矛盾,故證。
AC程式碼:
#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
const int maxn = 1e5 + 5;
char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch))
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int n, k, cnt, a[maxn], sum[maxn];
std::vector<int> g[maxn];
void init()
{
cnt = 0;
memset(sum, 0, sizeof(sum));
rep(i, 1, n) g[i].clear();
}
void dfs(int u, int f)
{
sum[u] ^= a[u];
for (auto v : g[u])
{
if (v == f)
continue;
dfs(v, u);
sum[u] ^= sum[v];
}
if (sum[u] == sum[0])
++cnt, sum[u] = 0;
}
bool solve()
{
n = read(), k = read();
init();
rep(i, 1, n) a[i] = read();
rep(i, 1, n - 1)
{
int u = read(), v = read();
g[u].push_back(v);
g[v].push_back(u);
}
rep(i, 1, n) sum[0] ^= a[i];
if (!sum[0])
return true;
if (sum[0] && k < 3)
return false;
dfs(1, 0);
return cnt >= 3 ? true : false;
}
int main(int argc, char const *argv[])
{
int T = read();
while (T--)
puts(solve() ? "YES" : "NO");
return 0;
}
E. Bored Bakry
題意:給你一個長度為 \(n\) 的序列 ,當一個子序列滿足 \(a_l \& a_{l+1} \& a_{l+2}...\& a_r>a_l \bigoplus a_{l+1} \bigoplus a_{l+2}...\bigoplus a_r\) 時,我們稱其為 \(good\) 子序列,現要你找出最長的 \(good\) 子序列。
題目分析:考慮什麼情況下會滿足一段區間的按位與 \(\& > \bigoplus\) ,根據按位與及異或和的性質,即存在二進位制下某一位 \(i\) ,有:
- 對所有 \(> i\) 的位二者相等,即這段子序列在高位的按位與及字首異或和為 \(0\)
- 在第 \(i\) 位上 \(\& > \bigoplus\) ,進一步可以推斷出序列長為偶數,因為要讓第 \(i\) 按位與佔優,那第 \(i\) 位必定全為 \(1\) ,此時要讓異或和較小,唯一的情況只有偶數個 \(1\) 異或
- 第 \(i\) 位上已有 \(\& > \bigoplus\) ,故 \(< i\) 的位不作考慮
接下來就是程式碼實現,我們從高位向低位列舉每一位二進位制 \(i\) ,得到一個新的 \(01\) 序列 \(b_i\) ,然後從 \(b_i\) 中選出每一段連續的 \(1\) 去更新答案,更新時我們記錄字首異或和與每一個異或值第一次出現的位置,這樣就能同時保證選出的 \(good\) 序列長度為偶數且字首異或和為 \(0\) ,原因的話我在程式碼中有註釋。
筆者水平有限(無論語文還是寫題),有疑問或更好的做法請在評論區留言,不足之處請諒解,謝謝
AC程式碼:
#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
const int maxn = 1e6 + 5;
char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch))
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int n, ans, a[maxn], b[maxn], sum[maxn], pos[maxn];
int solve(int l, int r)
{
if (l >= r)
return 0;
//記錄字首異或和
sum[l - 1] = 0;
rep(i, l, r) sum[i] = sum[i - 1] ^ b[i];
//記錄每一個異或值第一次出現的位置
//只有高位存在偶數個 1 ,即字首異或和為 0 的情況下
//sum才可能出現重複,pos才會發生更新
down(i, r, l - 1) pos[sum[i]] = i;
int res = 0;
//異或和的性質,a4 ^ a5 ^ a6 = (a1 ^ a2 ^ a3) ^ (a1 ^ a2 ^...^ a6)
//即 sum[l-1] = sum[r] ,則 [l,r] 的異或和為 0 ,保證了偶數段長度
rep(i, l, r) res = std::max(res, i - pos[sum[i]]);
return res;
}
int main(int argc, char const *argv[])
{
n = read();
rep(i, 1, n) a[i] = read();
down(i, 20, 0)
{
//列舉每一位
rep(j, 1, n) b[j] = a[j] >> i;
int l = 1, r = 1;
while (r <= n)
{
//將連續段 1 選出來
while (l <= n && !(b[l] & 1))
++l;
r = l;
while (r <= n && (b[r] & 1))
++r;
ans = std::max(ans, solve(l, r - 1));
l = r;
}
}
printf("%d\n", ans);
return 0;
}