從這裏開始
Problem A XOR Circle
你發現,權值的循環節爲 $a_0, a_1, a_0\oplus a_1$,然後暴力即可。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e5 + 5; int n; int a[N], b[N]; void quitif(boolean condition, const char* vert = "No") { if (condition) { puts(vert); exit(0); } } boolean check(int x, int y) { b[0] = x, b[1] = y; for (int i = 2; i < n; i++) b[i] = b[i - 1] ^ b[i - 2]; if (b[n - 2] ^ b[n - 1] ^ x) return false; if (b[n - 1] ^ b[0] ^ y) return false; sort(b, b + n); for (int i = 0; i < n; i++) if (b[i] ^ a[i]) return false; return true; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", a + i); } sort(a, a + n); int cnt = 1; for (int i = 1; i < n; i++) { cnt += (a[i] != a[i - 1]); } quitif(cnt > 3); if (cnt == 1) { if (check(0, 0)) { puts("Yes"); } else { puts("No"); } } else if (cnt == 2) { if (check(a[0], a[n - 1])) { puts("Yes"); } else { puts("No"); } } else { vector<int> qaq {a[0]}; for (int i = 1; i < n; i++) { if (a[i] ^ a[i - 1]) { qaq.push_back(a[i]); } } if (check(qaq[0], qaq[1]) || check(qaq[0], qaq[2]) || check(qaq[1], qaq[2])) { puts("Yes"); } else { puts("No"); } } return 0; }
Problem B Even Degrees
如果邊數是奇數,顯然無解。
如果是一棵樹,那麼從葉節點開始構造,每次選擇當前點到父節點的那條邊,使得子樹內所有點的出度都是偶數。不是樹的話,非樹邊隨便選。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e5 + 5; int n, m; int uf[N]; int d[N]; vector<int> G[N]; int find(int x) { return uf[x] == x ? x : (uf[x] = find(uf[x])); } void dfs(int p, int fa) { for (auto e : G[p]) { if (e ^ fa) { dfs(e, p); } } if (!fa) return; if (d[p]) { d[p] ^= 1; printf("%d %d\n", p, fa); } else { d[fa] ^= 1; printf("%d %d\n", fa, p); } } int main() { scanf("%d%d", &n, &m); if (m & 1) { puts("-1"); return 0; } for (int i = 1; i <= n; i++) uf[i] = i; for (int i = 1, u, v; i <= m; i++) { scanf("%d%d", &u, &v); if (find(u) ^ find(v)) { uf[find(u)] = find(v); G[u].push_back(v); G[v].push_back(u); } else { printf("%d %d\n", u, v); d[u] ^= 1; } } dfs(1, 0); assert(!d[1]); return 0; }
Problem C Skolem XOR Tree
講一個垃圾做法。
如果 $n = 2^k\ (k\geqslant 0)$,顯然無解。
如果 $n$ 爲奇數,那麼首先可以造出 $1 - 2 - 3 - 1' - 2' - 3'$。然後 $i - (i +1) - [i \oplus (i + 1)]- i' - (i + 1)' \ (2 | i)$。因爲它們最高位相同,所以 $i \oplus (i + 1)$ 總是小於 $i$。
如果 $n$ 爲偶數,設 $2^k < n < 2^{k + 1}$, 設 $n$ 的lowbit 爲 $x$,考慮 $i \oplus (i - 1) < 2^k$,因爲 $k > 1$,所以 $1 \oplus 2 \oplus \cdots \oplus (2^k - 1) = 0$,可以把它們按一定順序排列來構造。把 $n, n - 1, x, x - 1$ 按某種順序排列可以輕鬆構造出 $n - x$,這樣大於等於 $2^k$ 的數還有偶數個沒有加到樹上,把它們兩兩配對,然後按奇數的後半部分構造就可以了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e5 + 5; int n; void O(int x, int y) { printf("%d %d\n", x, y); } int main() { scanf("%d", &n); if (!(n - (n & (-n)))) { puts("No"); return 0; } puts("Yes"); if (n & 1) { O(1, 2); O(2, 3); O(3, 1 + n); O(1 + n, 2 + n); O(2 + n, 3 + n); for (int i = 4; i < n; i += 2) { int v = i ^ (i + 1); O(i, i + 1); O(i + 1, v); O(v, i + n); O(i + n, i + n + 1); } } else { int x = (n & (-n)) - 1; int y = x + 1; assert(!(x ^ y ^ (n - 1) ^ n)); O(y, x); O(x, n - 1); O(n - 1, n); O(n, y + n); O(y + n, x + n); O(x + n, n + n - 1); O(n + n - 1, n + n); int hig = 1; while ((hig << 1) < n) hig <<= 1; vector<int> a; for (int i = 1; i < hig; i++) { a.push_back(i); } a.erase(find(a.begin(), a.end(), x)); a.erase(find(a.begin(), a.end(), y)); for (int i = 1; i < (signed) a.size(); i++) O(a[i - 1], a[i]); O(a.back(), x); O(y, a[0] + n); for (int i = 1; i < (signed) a.size(); i++) O(a[i - 1] + n, a[i] + n); a.clear(); int z = x ^ (n - 1); O(z, x); O(n - 1, z + n); for (int i = hig; i < n - 1; i++) a.push_back(i); a.erase(find(a.begin(), a.end(), z)); assert(!(a.size() & 1)); for (int i = 1; i < (signed) a.size(); i += 2) { int v = a[i - 1] ^ a[i]; O(a[i - 1], a[i]); O(a[i], v); O(v, a[i - 1] + n); O(a[i - 1] + n, a[i] + n); } } return 0; }
Problem D Add and Remove
不難發現,一個數的貢獻係數並不多。
設 $f_{l, r, cl, cr}$ 表示,消除 $l, r$ 中的數,對左邊的貢獻爲 $cl$ ,對右邊的貢獻爲 $cr$。
轉移考慮枚舉最後刪掉的數,那麼它左側的貢獻係數變爲 $(cl, cl + cr)$,右側的貢獻係數爲 $cl + cr, cr)$。
粗略分析可得一個上界 $O(n^3 2^n)$。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; typedef class Status { public: int l, r, cl, cr; Status() { } Status(int l, int r, int cl, int cr) : l(l), r(r), cl(cl), cr(cr) { } boolean operator < (Status b) const { if (l ^ b.l) return l < b.l; if (r ^ b.r) return r < b.r; if (cl ^ b.cl) return cl < b.cl; return cr < b.cr; } } Status; #define ll long long int n; int a[20]; map<Status, ll> F; ll dp(int l, int r, int cl, int cr) { if (l > r) return 0; if (l == r) return 1ll * a[l] * (cl + cr); Status scur (l, r, cl, cr); if (F.count(scur)) return F[scur]; ll rt = 1e18; for (int k = l; k <= r; k++) { ll cost = a[k] * 1ll * (cl + cr); cost += dp(l, k - 1, cl, cl + cr); cost += dp(k + 1, r, cl + cr, cr); rt = min(rt, cost); } return F[scur] = rt; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); } if (n == 2) { printf("%d", a[1] + a[2]); return 0; } ll ans = dp(2, n - 1, 1, 1) + a[1] + a[n]; printf("%lld\n", ans); return 0; }
Problem E Develop
考慮 $x$ 向 $x - 2$ 以及 $x + K$ 連一條有向邊。存在方案當且僅當不存在環。充分性考慮不存在環意味着存在拓撲序,按拓撲序操作即可。必要性考慮如果存在環,並且能夠操作,那麼考慮在這個環上的最後一次操作會使得環上出現一個數,這會和最後一次操作矛盾。
如果 $K$ 是偶數,那麼奇數位和偶數位是獨立的,不存在環當且僅當在奇數位置或偶數位置中沒有不選長度超過 $K / 2$ 的段。
如果 $K$ 是奇數,不難發現如果存在環,一定存在大小爲 $K + 2$ 的環。考慮把所有數分成兩列,第一列是所有奇數,第二列是所有偶數,如果存在從左邊向下和向右的長度爲 $K + 2$ 的路徑,那麼一定存在長度爲 $K + 2$ 的環,考慮左邊從 $x$ 走到了 $x - 2l$,然後走到了 $x - 2l + K$,然後走到 $x - 2l + K - 2(K - l) = x - K$ 此時它加上 $K$ 等於 $x$。
考慮讓偶數 $x$ 和 $x - K$ 對齊,設 $f_{i, j, k}$ 表示考慮前 $i$ 行,右邊連續不選了 $j$ 個數,從左側最後一個點開始只能下或向右走的最長的長度爲 $k$。轉移討論一下兩邊選不選。
時間複雜度 $O(n^3)$
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 155; #define ll long long int n, K, K2, Mod; template <typename T> void fix(T& x) { (x >= Mod) && (x -= Mod); } namespace subtask1 { int f[N][N]; void solve() { int lim = (n + 1) >> 1; f[0][0] = 1; for (int i = 1; i <= lim; i++) { for (int j = 0; j < i && j <= K2; j++) { fix(f[i][j + 1] += f[i - 1][j]); fix(f[i][0] += f[i - 1][j]); } } int ans0 = 0, ans1 = 0; for (int i = 0; i <= K2; i++) fix(ans0 += f[n >> 1][i]); for (int i = 0; i <= K2; i++) fix(ans1 += f[lim][i]); ans0 = 1ll * ans0 * ans1 % Mod; printf("%d\n", ans0); } } namespace subtask2 { int f[N][N][N]; void solve() { f[0][0][0] = 1; for (int i = 1; i <= n + 1; i++) { int r = i << 1, l = r - K; boolean have_r = (r >= 1 && r <= n), have_l = (l >= 1 && l <= n); for (int j = 0; j < i; j++) { for (int k = 0; k <= K + 1; k++) { int v = f[i - 1][j][k]; if (!v) continue; #define F(nj, nk) ((nk) <= K + 1) && (fix(f[i][nj][(nk)] += v), 0) fix(f[i][0][0] += v); if (have_r) fix(f[i][j + 1][0] += v); if (have_l) F(0, (k ? k + 1 : 0)); if (have_l && have_r) F(j + 1, max(j + 2, k + 1)); } } } printf("%d\n", f[n + 1][0][0]); } } int main() { scanf("%d%d%d", &n, &K, &Mod); K2 = K >> 1; if (K & 1) { subtask2::solve(); } else { subtask1::solve(); } return 0; }
Problem F Two Histograms
定義一個局面是基本局面當且僅當不存在 $(i, j)$,使得 $k_i + 1 = j, l_j = i$,出現這樣的情況可以變成 $k_i + 1, l_j - 1$。不難證明對於每種局面可以轉化到至少一個基本局面。
可以發現,任意兩個基本局面都是不同的。
假設存在連兩個基本局面 $(k_1, k_2, \cdots, k_n, l_1, \cdots, l_m)$ 和 $(k'_1, \cdots, k'_2, l'_1, \cdots, l'_m)$ 相同。設 $j$ 是最小的使得 $l_j \neq l'_j$ 的整數,不妨設 $l_j < l'_j$。因爲它們產生的網格相同,那麼有:
- 如果 $j = 1$,那麼有 $A_{l'_1, 1} = 1$,那麼 $k'_{l'_1} + 1 = 1$,矛盾。
- 如果 $j > 1$,那麼有 $A_{l'_j, j} = 1$,那麼有 $k_{l'_j} < j, k_{l'_j} \geqslant j$,如果 $k_{l'_j} = j - 1$,那麼矛盾,否則 $A_{l'_j, j - 1}$ 不可能相等,矛盾。
因此只用對基礎局面進行計數,這個容斥即可。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 5e5 + 5; #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 = 998244353; 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; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; 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; int n, m; Zi fac[N], _fac[N]; void init_fac(int n) { 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; } } Zi comb(int n, int m) { return fac[n] * _fac[m] * _fac[n - m]; } Zi pwn[N], pwm[N]; int main() { scanf("%d%d", &n, &m); init_fac(max(n, m)); pwn[0] = 1; for (int i = 1; i <= m; i++) pwn[i] = pwn[i - 1] * (n + 1); pwm[0] = 1; for (int i = 1; i <= n; i++) pwm[i] = pwm[i - 1] * (m + 1); Zi ans = 0; for (int i = 0; i <= n && i <= m; i++) { Zi tmp = comb(n, i) * comb(m, i) * fac[i] * pwn[m - i] * pwm[n - i]; if (i & 1) { ans -= tmp; } else { ans += tmp; } } printf("%d\n", ans.v); return 0; }