從這裏開始
因爲巨大多無可奉告的原因,所以咕得非常厲害。有空再補 EF 好了。
Problem A Xor Battle
考慮如果 1 能選的某個數 $a$ 後面 0 能選的數都能表示出它,顯然是 0 必勝。
否則考慮考慮到這一位的異或和爲 $s$,如果 0 能獲勝,那麼說明 $s XOR a$ 以及 $s$ 都能被後面的數線性表示出來,這推出 $a$ 能被線性表示出來,這顯然和條件矛盾。因此此時 1 必勝。
感覺做複雜了,去看瞄了一眼題解,直接 dp 用線性基維護一下 dp 值爲真的位置就行了。不會 dp 石錘了。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long const int bzmax = 60; typedef class LinearBasis { public: ll a[bzmax]; void insert(ll x) { for (int i = bzmax; i-- && x; ) { if ((x >> i) & 1) { x ^= a[i]; } if ((x >> i) & 1) { a[i] = x; break; } } } bool query(ll x) { for (int i = bzmax; i-- && x; ) { if ((x >> i) & 1) { x ^= a[i]; } } return !x; } void reset() { memset(a, 0, sizeof(a)); } } LinearBasis; const int N = 205; int T, n; ll a[N]; char s[N]; LinearBasis lb; void solve() { lb.reset(); scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%lld", a + i); } scanf("%s", s + 1); for (int i = n; i; i--) { if (s[i] == '1') { if (!lb.query(a[i])) { puts("1"); return; } } else { lb.insert(a[i]); } } puts("0"); } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }
Problem B 01 Unbalanced
當沒有通配符的時候,把 0 替換爲 -1,考慮前綴和,顯然答案是最大前綴和和最小前綴和的差。
考慮先把通配符全部填成 1。從右到左依次考慮每個通配符填成的 1,如果把它換成 -1 能夠使得最大的和減少 2,那麼就換。假如不操作這一步,那麼把之後操作的一步放到這裏來顯然不會更劣。
這樣有點小問題,因爲可以使得最大的和減少 1。首先可以發現這樣的次數至多 1 次,因爲操作了兩次就把前一次刪掉,顯然不會更劣。容易發現操作的這一次位置應該儘量靠右。
Code
#include <bits/stdc++.h> using namespace std; const int N = 1e6 + 5; int n; char s[N]; int sum[N], pmx[N], pmi[N]; int main() { scanf("%s", s + 1); n = strlen(s + 1); sum[0] = 0; pmx[0] = 0; pmi[0] = 0; for (int i = 1; i <= n; i++) { sum[i] = sum[i - 1] + 1 - 2 * (s[i] == '0'); pmx[i] = max(pmx[i - 1], sum[i]); pmi[i] = min(pmi[i - 1], sum[i]); } int suf_mi = sum[n], suf_mx = sum[n]; int ans = pmx[n] - pmi[n]; for (int i = n; i; i--) { suf_mi = min(suf_mi, sum[i]); suf_mx = max(suf_mx, sum[i]); if (s[i] == '?') { if (pmx[i - 1] + 1 < suf_mx) { suf_mx -= 2; suf_mi -= 2; ans = min(ans, max(pmx[i - 1], suf_mx) - min(pmi[i - 1], suf_mi)); } } } suf_mi = sum[n], suf_mx = sum[n]; bool found = false; for (int i = n; i; i--) { suf_mi = min(suf_mi, sum[i]); suf_mx = max(suf_mx, sum[i]); if (s[i] == '?') { if (pmx[i - 1] + 1 == suf_mx) { ans = min(ans, max(pmx[i - 1], suf_mx - 2) - min(pmi[i - 1], suf_mi - 2)); if (!found) { suf_mi -= 2; suf_mx -= 2; found = true; } } else if (pmx[i - 1] + 1 < suf_mx) { suf_mx -= 2; suf_mi -= 2; ans = min(ans, max(pmx[i - 1], suf_mx) - min(pmi[i - 1], suf_mi)); } } } printf("%d\n", ans); return 0; }
Problem C Range Set
不妨設 $A > B$,顯然對於每種局面存在一種方案使得至少使用一次 $A$ 操作。
如果能夠進行一次 $A$ 操作,從左到右填出最後一次左邊的顏色,再從右到左填出右邊的顏色即可。
然後考慮計算一次 $A$ 操作都進行不了的方案數,這個等價於計算任意兩個相鄰的 1 的長度小於 $B$ 的連續段之間的長度小於 $A$。
這個簡單 dp 一下就好了。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 1e9 + 7; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 5005; int n, A, B; Zi f[N], g[N][2]; int main() { scanf("%d%d%d", &n, &A, &B); if (A > B) { swap(A, B); } f[0] = 1; for (int i = 1; i <= B; i++) { f[i] = f[i - 1]; for (int j = A; j <= i; j++) { if (j == i) { f[i] += 1; } else { f[i] += f[i - j - 1]; } } } g[0][0] = 1; for (int i = 1; i < B; i++) { g[i][0] = f[i - 1]; } for (int i = 1; i <= n; i++) { for (int j = 1; j < B && j <= i; j++) { g[i][0] += g[i - j][1] * ((j == 1) ? 1 : f[j - 2]); } for (int j = 1; j < A && j <= i; j++) { g[i][1] += g[i - j][0]; } } Zi ans = g[n][1]; for (int i = 1; i < B; i++) { ans += g[n - i][1] * f[i - 1]; } ans = qpow(2, n) - ans; printf("%d\n", ans.v); return 0; }
Problem D Lamps and Buttons
排序排列對應的若干個環。顯然每個燈屬於的環必選包含前 $A$ 箇中的某一個。
考慮最優策略顯然是:當還未全點亮時,選擇一個點亮的燈,操作它, 如果它熄滅了就失敗,否則顯然可以點亮它所在的環。
所以問題等價於計算滿足滿足下列條件的環排列劃分的方案數:
- 每個環都包含至少一個點亮的燈
- 第一個自環後面的環只包含前 $A$ 個燈中的燈。
考慮枚舉第一個自環的位置,然後對前面每個環的大小都大於 1 進行容斥。
首先計算出 $f_{i, j}$ 表示考慮前 $i$ 個燈,有 $j$ 個被硬點是自環的方案數。
然後考慮怎麼計算答案。注意到計算答案之和 $j$ 有關,設 $t_i$ 表示當有 $j$ 個燈被硬點爲自環時,將剩下的燈加入環排列的方案數。
那麼考慮 $f_{i - 1, j}$ 貢獻到答案的係數。枚舉後面 $A - i$ 個燈有多少個插入前 $i$ 個的環排列中,那麼有:
$$
\sum_{k=0}^{A-i}(i - j - 1)^{\overline{k}} \binom{A-i}{k} (A - i - k)! t_{j + 1 + m - i}
$$
將組合數拆成階乘表示的形式,然後提出 $(A - i)!$,然後設 $d = i - j, l = m - i$,很容易在 $O(n^2)$ 的時間內預處理係數。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 1e9 + 7; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 1e7 + 5, M = 5005; vector<Zi> fac, _fac; Zi comb[M][M]; void init_fac(int n) { fac.resize(n + 1); _fac.resize(n + 1); fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = fac[i - 1] * i; } _fac[n] = ~fac[n]; for (int i = n; i; i--) { _fac[i - 1] = _fac[i] * i; } } void init(int n) { comb[0][0] = 1; for (int i = 1; i <= n; i++) { comb[i][0] = 1; for (int j = 1; j <= i; j++) { comb[i][j] = comb[i - 1][j - 1] + comb[i - 1][j]; } } } int n, m; Zi f[M][M], g[M][M], t[M]; int main() { scanf("%d%d", &n, &m); init_fac(n); init(m); for (int i = 0; i < m; i++) { t[i] = fac[n - 1 - i] * _fac[m - 1 - i]; } for (int d = 1; d <= m; d++) { g[d][0] = t[m - d + 1]; if (d == 1) { for (int j = 1; d + j <= m; j++) { g[d][j] = g[d][j - 1]; } } else { for (int j = 1; d + j <= m; j++) { g[d][j] = g[d][j - 1] + fac[d + j - 2] * _fac[d - 2] * _fac[j] * t[m - d + 1 - j]; } } for (int j = 1; d + j <= m; j++) { g[d][j] *= fac[j]; } } f[0][0] = 1; Zi ans = 0; for (int i = 1; i <= m; i++) { f[i][0] = f[i - 1][0] * i; for (int j = 1; j <= i; j++) { f[i][j] = f[i - 1][j] * (i - j) - f[i - 1][j - 1]; } for (int j = 0; j < i; j++) { ans += f[i - 1][j] * g[i - j][m - i]; } } for (int i = 0; i < m; i++) { ans += f[m][i] * t[i]; } printf("%d\n", ans.v); return 0; }
Problem E Fragile Balls
挖坑待填,明天再說.jpeg
Problem F Division into Multiples
挖坑待填,明天再說.jpeg