比賽鏈接
官方題解
Problem A. Xor Battle
考慮維護使得最後一個玩家獲勝的數字集合 ,初始時, 。
考慮最後一個回合 :
若該回合是 0 號玩家的回合,則有 ;
若該回合是 1 號玩家的回合,則若 ,則 ,否則, 。
這是因爲 是一個包含 的線性空間,因此若 ,則對於所有 ,有 。
那麼,維護 的線性基,支持插入和詢問某個元素是否在 中即可。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 205;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
ll a[MAXN], b[MAXN], bit[MAXN];
char s[MAXN];
void ins(ll x) {
for (int i = 60; i >= 0; i--)
if (x & bit[i]) {
if (b[i] == 0) {
b[i] = x;
break;
}
x ^= b[i];
}
}
bool query(ll x) {
for (int i = 60; i >= 0; i--)
if (x & bit[i]) x ^= b[i];
return x == 0;
}
int main() {
int T; read(T);
while (T--) {
int n; read(n);
for (int i = 1; i <= n; i++)
read(a[i]);
scanf("\n%s", s + 1);
for (int i = 0; i <= 60; i++)
b[i] = 0, bit[i] = 1ll << i;
bool win = false;
for (int i = n; i >= 1; i--) {
if (s[i] == '0') ins(a[i]);
else win |= !query(a[i]);
}
if (win) puts("1");
else puts("0");
}
return 0;
}
Problem B. 01 Unbalanced
考慮將 0 看做 ,將 1 看做 ,求出各個位置的前綴和 。
可以發現,問題要求我們最小化 集合的極差。
考慮枚舉 集合的最小值 ,保證 均 的情況 下最小化最大值 。
則應當首先將所有的 ? 替換爲 1 ,若此時,仍然存在 ,則顯然不存在合法解。
否則,可以發現從前到後貪心地決定是否將一個 ? 修改爲的 1 變成 0 是最優的。
則可以通過預處理 後綴最小值的方式 解決該問題。
記 集合最小值的最大值爲 ,枚舉 可以得到一個 的做法。觀察貪心的過程,可以發現 的解不會優於 的解,從而可以只枚舉 ,降低時間複雜度。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
char s[MAXN];
int n, a[MAXN], sMin[MAXN]; bool b[MAXN];
void init() {
for (int i = 1; i <= n; i++) {
if (s[i] == '0') a[i] = a[i - 1] - 1;
else a[i] = a[i - 1] + 1;
if (s[i] == '?') b[i] = true;
}
sMin[n + 1] = n + 5;
for (int i = n; i >= 0; i--)
sMin[i] = min(sMin[i + 1], a[i]);
}
int work(int limit) {
int mns = 0;
for (int i = 1; i <= n; i++) {
if (s[i] == '?' && sMin[i] - mns - 2 >= limit) {
b[i] = false;
mns += 2;
}
a[i] -= mns;
}
int Max = 0;
for (int i = 1; i <= n; i++)
chkmax(Max, a[i]);
return Max - limit;
}
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
int ans = n + 5, tmp = 0;
init(); chkmin(ans, work(sMin[0]));
init(); chkmin(ans, work(sMin[0] - 1));
cout << ans << endl;
return 0;
}
Problem C. Range Set
首先,我們顯然可以將整個序列染成 1 ,因此,不妨令 。
考慮一個結果序列 ,判斷其是否可以到達。
對於一個長度至少爲 的全 0 區間,我們可以將其任意染成一種顏色。
對於一個長度至少爲 的全 1 區間,我們可以將其任意染成一種顏色。
由此,若將長度至少爲 的全 0 區間全部染成 1 後,存在長度至少爲 的全 1 區間,則結果序列 可以被達到。反之,不難證明,結果序列 不能被達到。
那麼,記 表示長度爲 的區間,結尾處極長的全 1 區間的長度爲 ,轉移時枚舉下一個 1 的位置,可以得到一個 的做法。
觀察轉移形式,用部分和優化即可。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, a, b, dp[MAXN][MAXN], two[MAXN];
int pd[MAXN][MAXN], aux[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int main() {
read(n), read(a), read(b);
if (a < b) swap(a, b); two[0] = 1;
for (int i = 1; i <= n; i++)
two[i] = 2ll * two[i - 1] % P;
dp[0][0] = 1;
for (int i = 0; i <= n; i++) {
if (i != 0) update(aux[i], aux[i - 1]);
update(dp[i][1], aux[i]);
for (int j = 1; j <= a; j++) {
update(dp[i][j], pd[i][j]);
update(pd[i + 1][min(j + 1, a)], pd[i][j]);
}
if (i == n) break;
for (int j = 0; j <= a - 1; j++) {
update(dp[i + 1][j + 1], dp[i][j]);
update(aux[i + 2], dp[i][j]);
if (i + b + 1 <= n) update(aux[i + b + 1], P - dp[i][j]);
if (i + b + 1 <= n) update(pd[i + b + 1][min(j + b + 1, a)], dp[i][j]);
if (n - i >= b) update(dp[n][min(j + n - i, a)], dp[i][j]);
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
update(ans, 1ll * dp[i][a] * two[n - i] % P);
cout << ans << endl;
return 0;
}
Problem D. Lamps and Buttons
考慮 Snuke 的最優策略,應當爲:
隨機操作一盞亮着的燈
若 ,則 永遠不會再次亮起,遊戲結束, Snuke 失敗。
若 ,則若 暗了下去,則可以再次操作 ,使其亮起;若 亮了起來, Snuke 可以轉而操作 ,直至將 所在的置換環全部點亮。
由於排列 的隨機性,我們可以認爲 Snuke 每次隨機都會操作最小的尚未操作的燈。
那麼,一個排列合法當且僅當其滿足如下條件:
令 表示最小的,使得 的位置,若不存在,則令 ,
對於所有 , 所在的置換環上存在一個 的元素。
考慮如何計數,由於 較大,不妨考慮將置換環中 的元素刪去,考慮剩餘的置換。
此時,在 之前,可能會有其他的滿足 的自環,但在插入 的元素後,這些自環將會被補足爲一個至少二元的環。在枚舉 後,我們需要知道兩點:
、滿足所在置換環中存在 的元素的位置個數
、滿足 ,且 的位置個數
考慮將 的元素插入進置換的合法方案數,應當爲
同時,對於所在置換環中不存在 的元素的 個位置,其置換的形式是任意的,即有係數
由此,我們可以設計動態規劃,將上文的 計入狀態。
轉移時枚舉最小的元素所在的置換環的大小,不難得到一個 的解法。
觀察轉移,用部分和優化,時間複雜度降爲 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e7 + 5;
const int MAXM = 5e3 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int fac[MAXN], inv[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int n, m, dp[MAXM][MAXM], sum[MAXM][MAXM];
int func(int x, int y) {
return 1ll * fac[x] * inv[x - y] % P;
}
int main() {
read(n), read(m), init(n);
dp[0][0] = 1, sum[0][0] = fac[m - 1];
for (int i = 1; i <= m; i++)
for (int j = 0; j <= i; j++) {
update(dp[i][j], dp[i - 1][j - 1]);
if (i >= 2) update(dp[i][j], 1ll * sum[i - 2][j] * inv[m - i] % P);
sum[i][j] = (sum[i - 1][j] + 1ll * dp[i][j] * fac[m - i - 1]) % P;
}
int ans = 0;
for (int i = 0; i <= m; i++)
for (int j = 0; j <= i && j <= n - m; j++)
update(ans, 1ll * dp[i][j] * fac[max(m - i - 1, 0)] % P * func(n - m, j) % P * func(i + (n - m - j) - 1, n - m - j) % P);
cout << ans << endl;
return 0;
}
Problem E. Fragile Balls
令 表示滿足 的球數。
考慮 的情況,此時,答案或是 ,或是 。
考慮對於每個球,建邊 ,由題目條件,各個點的入度均不爲 。
定義連通塊爲將有向邊看做無向邊後,形成的連通塊,則可以將連通塊分爲如下三類:
、連通塊中僅包含一個點,和一條指向自己的邊
、連通塊中僅包含一個長度 的環,且不存在其它的邊
、連通塊中的邊數多於點數
可以發現,若存在第 類連通塊,答案顯然爲 。
同時,若不存在第 類連通塊,由各個點的入度均不爲 的性質,我們可以按照拓撲序構造一組合法的方案。從而,答案爲 當且僅當不存在第 類連通塊。
考慮原有的問題,在 的情況下,即使存在第 類連通塊,也可能有解。
我們可以將一個連通塊外的球 移入第 類連通塊,從而處理該連通塊。
那麼,記第 類連通塊的個數爲 ,我們必然需要額外花費 步。
考慮球 的如下幾種情況:
、 在第 類連通塊內,此時,若要將 移出,必須將另一個球移入 所在的位置,即將 所在的連通塊看做第 類連通塊處理。這會導致 增加 ,同時,我們也可以處理掉 個連通塊,總的效果是導致 減去 ,並且額外花費 步。
、 在第 類連通塊內,我們可以在所在連通塊被處理時將 移出,處理其餘連通塊,並在 移回時繼續處理該連通塊。總的效果是導致 減去 ,沒有額外花費步數。
、 在第 類連通塊內, ,效果是導致 減去 ,並且額外花費 步。
、 在第 類連通塊內, ,效果是導致 減去 ,沒有額外花費步數。
同時,由於第 類連通塊中的元素初始時是不能動的,若 ,必須存在至少一個屬於情況 的球 。剩餘的問題就變爲了:給定若干個代價 的物品,求出使得物品總價值 的最小代價。排序後枚舉代價爲 的物品個數,用雙指針計算答案即可。
時間複雜度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 1e9 + 7;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
vector <int> res[3];
bool cycle[MAXN]; int x[MAXN], y[MAXN], c[MAXN];
int n, m, ind[MAXN], oud[MAXN], f[MAXN], s[MAXN];
int find(int x) {
if (f[x] == x) return x;
else return f[x] = find(f[x]);
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) {
f[y] = x;
cycle[y] = false;
s[x] += s[y];
}
}
bool cmp(int x, int y) {
return x > y;
}
int work(ll cnt, bool mode) {
sort(res[0].begin(), res[0].end(), cmp);
sort(res[1].begin(), res[1].end(), cmp);
sort(res[2].begin(), res[2].end(), cmp);
for (auto x : res[0]) cnt -= x;
int ans = INF;
if (mode == false) {
if (res[1].empty()) return INF;
cnt -= res[1][0];
res[1].erase(res[1].begin());
int cur = res[2].size(); ll sum = 0;
for (auto x : res[2]) sum += x;
while (cur >= 1 && sum - res[2][cur - 1] >= cnt)
sum -= res[2][--cur];
if (sum >= cnt) chkmin(ans, 1 + cur * 2);
for (int i = 0; i < res[1].size(); i++) {
cnt -= res[1][i];
while (cur >= 1 && sum - res[2][cur - 1] >= cnt)
sum -= res[2][--cur];
if (sum >= cnt) chkmin(ans, 1 + (i + 1) + cur * 2);
}
return ans;
} else {
if (cnt <= 0) return 0;
int cur = res[2].size(); ll sum = 0;
for (auto x : res[2]) sum += x;
while (cur >= 1 && sum - res[2][cur - 1] >= cnt)
sum -= res[2][--cur];
if (sum >= cnt) chkmin(ans, cur * 2);
for (int i = 0; i < res[1].size(); i++) {
cnt -= res[1][i];
while (cur >= 1 && sum - res[2][cur - 1] >= cnt)
sum -= res[2][--cur];
if (sum >= cnt) chkmin(ans, (i + 1) + cur * 2);
}
return ans;
}
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
f[i] = i, s[i] = 1, cycle[i] = true;
for (int i = 1; i <= m; i++) {
read(x[i]), read(y[i]), read(c[i]);
merge(x[i], y[i]), oud[x[i]]++, ind[y[i]]++;
}
for (int i = 1; i <= n; i++)
if (ind[i] != 1 || oud[i] != 1) cycle[find(i)] = false;
int cnt = 0, ans = 0;
for (int i = 1; i <= n; i++)
if (f[i] == i) cnt += cycle[i] && s[i] >= 2;
ll lft = cnt; bool key = false;
for (int i = 1; i <= m; i++) {
ans += x[i] != y[i];
if (c[i] >= 2) {
if (cycle[find(x[i])]) {
if (s[find(x[i])] == 1) res[2].push_back(c[i] - 2);
else res[0].push_back(c[i] - 1);
} else {
if (x[i] == y[i]) res[1].push_back(c[i] - 1);
else {
lft -= c[i] - 1;
key = true;
}
}
}
}
if (cnt == 0) {
cout << ans << endl;
return 0;
} else {
int tmp = work(lft, key);
if (tmp == INF) cout << -1 << endl;
else cout << ans + cnt + tmp << endl;
}
return 0;
}
Problem F. Division into Multiples
若 ,則可以令 ,從而 ;
若 ,則可以令 ,從而
若 ,則可以令 ,從而 。
若 或 ,則可以令 。
由此,問題轉化爲了 的情況。
考慮方程 ,令 ,其中 表示 在模 意義下的乘法逆元。則有通解:
注意到若存在兩組解 滿足 ,則 是不優的,我們不會使用。考慮求出在這個意義下,我們可能使用的那些解。
考慮如下子問題:給定 的平面,從原點處出發,一束光線沿第一象限角平分線方向發射,達到 時,令 ,達到 時,令 。我們希望求出 上被光束擊中時是前綴最大值的點。
考慮一個 的正方形,可以發現,光束從任意點射入,都將從其對面的點射出,從而可以刪去一個極大的,包含原點的正方形。利用類似歐幾里得算法的過程重複刪去正方形,我們可以得到所求的所有可能使用的解。它們在平面上形成了 段線段。
進一步思考上述算法的過程,我們還可以發現,這些解構成了一個下凸殼。
我們現在想要選擇若干個向量,滿足求和後,兩維均小於等於 。
由解集的凸性,我們一定只會使用將方向 夾在中間的兩種向量。
由此,二分答案,找到這兩種向量,判斷答案是否合法即可。
時間複雜度 或 。
以下代碼實現的是 的解法。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
void exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return;
}
int q = a / b, r = a % b;
exgcd(b, r, y, x);
y -= q * x;
}
int inv(int x, int p) {
int a = 0, b = 0;
exgcd(x, p, a, b);
return (a % p + p) % p;
}
struct info {int x, y, c; } q[MAXN];
int top, a, b, c, x, y;
void work(int c, int d){
q[top = 0] = (info) {0, 0, 0};
int x, a = 1, b = 0;
while (d != 0) {
info tmp = q[top];
q[++top] = (info) {tmp.x + c / d * a, tmp.y + c / d * d, c / d};
for (int t = 1; t <= 2 && d != 0; t++){
if (t == 1) b += c / d * a;
else a += c / d * b;
x = c % d, c = d, d = x;
}
}
}
ll func(ll x, ll y) {
if (x < 0) return -1;
else return x / y;
}
int main() {
int T; read(T);
while (T--) {
read(a), read(x), read(b), read(y), read(c);
int g = __gcd(a, b); a /= g, b /= g, c /= __gcd(c, g);
g = __gcd(a, c), a /= g, c /= g, y /= g;
g = __gcd(b, c), b /= g, c /= g, x /= g;
a %= c, b %= c;
if (c == 1) {
printf("%d\n", x + y);
continue;
}
int d = 1ll * a * inv(b, c) % c; work(c, d);
if (q[top].x != c) q[++top] = (info) {c, c, 1};
int ans = 0;
for (int i = 1; i <= top; i++) {
int lx = q[i - 1].x, rx = q[i].x;
int ly = c - q[i - 1].y, ry = c - q[i].y;
int dx = (rx - lx) / q[i].c, dy = (ly - ry) / q[i].c;
int l = ans + 1, r = x + y;
while (l <= r) {
int mid = (0ll + l + r) / 2;
ll s = func(x - 1ll * mid * lx, dx);
ll t = func(y - 1ll * mid * ry, dy);
if (s >= 0 && t >= 0 && s + t >= 1ll * q[i].c * mid) ans = mid, l = mid + 1;
else r = mid - 1;
}
}
printf("%d\n", ans);
}
return 0;
}