\(sroce : 100 + 100 + 10 + 12 + 24 + 40 = 286\),還需繼續加油鴨!ios
題解順序按本人心中的難度順序升序排序。c++
題目連接:Link。算法
- 1 位格雷碼由兩個 1 位二進制串組成,順序爲:0,1。
- \(n + 1\) 位格雷碼的前 \(2^n\) 個二進制串,能夠由依次算法生成的 \(n\) 位格雷碼(總共 \(2^n\) 個 \(n\) 位二進制串)按順序排列,再在每一個串前加一個前綴 0 構成。
- \(n + 1\) 位格雷碼的後 \(2^n\) 個二進制串,能夠由依次算法生成的 \(n\) 位格雷碼(總共 \(2^n\) 個 \(n\) 位二進制串)按逆序排列,再在每一個串前加一個前綴 1 構成。
經過上面的這段 " 一種格雷碼的生成算法 " 咱們能夠知道,對於任意的 \(n(n \geq 2)\),\(n\) 位格雷碼老是能夠由 \(n - 1\) 位格雷碼加一個前綴 0 或前綴 1 構成,考慮分治。數組
記 \(calc(n, k)\) 表示 \(n\) 位格雷碼中的 \(k\) 號二進制串。優化
首先是遞歸邊界 \(n = 1\),此時若 \(k = 0\),則 \(calc(n, k) = 0\);若 \(k = 1\),則 \(calc(n, k) = 1\)。spa
對於任意的 \(n(n \geq 2)\),此時有兩種狀況:code
時間複雜度 \(\mathcal{O(n)}\)。排序
記得開 unsigned long long
!繼承
題外話。遞歸
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <string> using namespace std; int n; unsigned long long k; string calc(int n, unsigned long long k) { if (n == 1) return k == 0 ? "0" : "1"; else { unsigned long long S = 1ull << (n - 1); if (k < S) return "0" + calc(n - 1, k); else return "1" + calc(n - 1, 2 * S - 1 - k); } } int main() { cin >> n >> k; cout << calc(n, k) << endl; return 0; }
題目連接:Link。
注意到答案的形式是 \((1 \times k_1) \ \text{xor} \ (2 \times k_2) \ \text{xor} \ (3 \times k_3) \ \text{xor} \ ⋯ \ \text{xor} \ (n \times k_n)\),這使得咱們難以對答案進行一些分析,只能乖乖地把 \(k_1, k_2, k_3, ..., k_n\) 都求出來,再計算出答案。
爲了方便敘述,約定變量:
()(())((()))
就能數出 \(3\) 個連續的括號塊。考慮對樹進行一次廣度優先遍歷,一開始只有 \(1\) 號節點。
每次從隊頭取出節點 \(u\),考慮 \(u\) 的每條出邊 \((u, v)\),考慮計算 \(k_v\)。
首先答案能夠先繼承,即咱們可讓 \(k_v = k_u\),而後再考慮一下將節點 \(v\) 上的括號加進 \(s_u\) 時產生的貢獻。
首先當節點 \(v\) 上的括號爲 (
時,不會產生貢獻。
考慮當節點 \(v\) 上的括號爲 )
時產生的貢獻,令 \(p = u\),咱們考慮讓 \(p\) 去暴力跳 \(lst\) 數組,來找出與節點 \(v\) 上的 )
匹配的 (
的位置:
(
,則匹配成功,結束匹配。當 \(p \neq 0\) 時,咱們就找到了與節點 \(v\) 上的 )
匹配的 (
的位置 \(p\)。
則 \(lst_v = p\),\(cnt_v = cnt_{fa_p} + 1\),而此時加入節點 \(v\) 上的括號對答案貢獻也即爲 \(cnt_v\) 了,由於以節點 \(v\) 爲結尾的連續的括號塊的每個後綴,都是合法括號串,一共有 \(cnt_v\) 個後綴。
注意到在跳的過程當中,每個括號塊只會被通過一次(要麼匹配成功,並創建了一個跨度更大的括號塊,要麼匹配失敗,跳的時候就不會再涉及到該括號塊),故時間複雜度 \(O(n)\)。
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int N = 500100; int n; char s[N]; int tot, head[N], ver[N], Next[N]; void add(int u, int v) { ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot; } int fa[N]; int lst[N]; int cnt[N]; long long k[N]; void bfs() { queue<int> q; q.push(1); memset(lst, -1, sizeof(lst)); while (q.size()) { int u = q.front(); q.pop(); for (int i = head[u]; i; i = Next[i]) { int v = ver[i]; q.push(v); k[v] = k[u]; if (s[v] == '(') continue; int p = u; while (p) { if (lst[p] == -1) { if (s[p] == ')') p = 0; break; } else p = fa[lst[p]]; } if (p) { lst[v] = p; cnt[v] = cnt[fa[p]] + 1; k[v] += cnt[v]; } } } } int main() { scanf("%d", &n); scanf("%s", s + 1); for (int i = 2; i <= n; i ++) { scanf("%d", &fa[i]); add(fa[i], i); } bfs(); long long ans = 0; for (int i = 1; i <= n; i ++) ans ^= k[i] * i; printf("%lld\n", ans); return 0; }`
題目連接:Link。
咱們能夠簡單容斥一下,先求出在 " 存在一種主要食材使用次數大於菜的一半 " 狀況下的方案數,再與上式作個差便可求出答案。
注意到有且僅有一種主要食材使用次數大於菜的一半,咱們能夠枚舉這個主要食材,記咱們枚舉的主要食材的編號爲 \(col\),對於每個主要食材,考慮 dp。
設 \(f_{i, j, k}\) 表示:在前 \(i\) 個烹飪方法中,作了 \(j\) 道菜,且第 \(col\) 種主要食材用了 \(k\) 個時的方案數。
設 \(S_i = \sum\limits_{j = 1}^m a_{i, j}\),根據題意,轉移有三種:
不使用第 \(i\) 個烹飪方法作菜。此時能夠從 " 在前 \(i - 1\) 個烹飪方法中,作了 \(j\) 道菜,且第 \(col\) 種主要食材用了 \(k\) 個 " 轉移而來,故該種轉移的方案數爲 \(f_{i - 1, j, k}\)。
使用第 \(i\) 個烹飪方法作菜,使用第 \(col\) 種主要食材。此時能夠從 " 在前 \(i - 1\) 個烹飪方法中,作了 \(j - 1\) 道菜,且第 \(col\) 種主要食材用了 \(k - 1\) 個 " 轉移而來,使用第 \(i\) 個烹飪方法且使用第 \(j\) 個主要食材能夠製做出 \(a_{i, col}\) 道菜,根據乘法原理,該種轉移的方案數爲 \(f_{i - 1, j - 1, k - 1} \ast a_{i, col}\)。
使用第 \(i\) 個烹飪方法作菜,不使用第 \(col\) 種主要食材。此時能夠從 " 在前 \(i - 1\) 個烹飪方法中,作了 \(j - 1\) 道菜,且第 \(col\) 種主要食材用了 \(k\) 個 " 轉移而來,使用第 \(i\) 個烹飪方法且不使用第 \(j\) 個主要食材能夠製做出 \(S_i - a_{i, col}\) 道菜,根據乘法原理,該種轉移的方案數爲 \(f_{i - 1, j - 1, k} \ast (S_i - a_{i, col})\)。
故有狀態轉移方程:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; inline int read() { int x = 0, f = 1; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); } while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); } return x * f; } const int N = 110, M = 2010, base = 100; const int mod = 998244353; int n, m; int a[N][M]; int S[N]; int f[N][N * 2]; int main() { n = read(), m = read(); for (int i = 1; i <= n; i ++) for (int j = 1; j <= m; j ++) a[i][j] = read(); for (int i = 1; i <= n; i ++) for (int j = 1; j <= m; j ++) S[i] = (S[i] + a[i][j]) % mod; int ans = 1; for (int i = 1; i <= n; i ++) ans = 1ll * ans * (S[i] + 1) % mod; ans = ((ans - 1) % mod + mod) % mod; for (int col = 1; col <= m; col ++) { memset(f, 0, sizeof(f)); f[0][0 + base] = 1; for (int i = 1; i <= n; i ++) for (int j = -i + base; j <= i + base; j ++) { int val = 0; val = (val + f[i - 1][j]) % mod; if (j) val = (val + 1ll * f[i - 1][j - 1] * a[i][col]) % mod; val = (val + 1ll * f[i - 1][j + 1] * (S[i] - a[i][col])) % mod; val = (val % mod + mod) % mod; f[i][j] = val; } for (int j = 1 + base; j <= n + base; j ++) ans = ((ans - f[n][j]) % mod + mod) % mod; } printf("%d\n", ans); return 0; }
題目連接:Link。
__int128
,#include <cstdio> #include <cstring> #include <algorithm> using namespace std; inline int read() { int x = 0, f = 1; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); } while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); } return x * f; } inline void print(__int128 x) { if (x > 9) print(x / 10); putchar('0' + x % 10); } const int N = 40001000, M = 100100; int n, type; int a[N]; void makedata() { static int x, y, z, b[N], m, p[M], l[M], r[M], mod = (1 << 30); x = read(), y = read(), z = read(), b[1] = read(), b[2] = read(), m = read(); p[0] = 0; for (int i = 1; i <= m; i ++) p[i] = read(), l[i] = read(), r[i] = read(); for (int i = 3; i <= n; i ++) b[i] = (1ll * x * b[i - 1] + 1ll * y * b[i - 2] + z) % mod; for (int j = 1; j <= m; j ++) for (int i = p[j - 1] + 1; i <= p[j]; i ++) a[i] = (b[i] % (r[j] - l[j] + 1)) + l[j]; } long long S[N]; int l, r; int q[N]; int dec[N]; long long suf(int x) { return S[x] - S[dec[x]]; } int main() { n = read(), type = read(); if (type == 1) makedata(); else for (int i = 1; i <= n; i ++) a[i] = read(); for (int i = 1; i <= n; i ++) S[i] = S[i - 1] + a[i]; l = 1, r = 1; q[1] = 0; int p = 0; for (int i = 1; i <= n; i ++) { while (l <= r && S[q[l]] + suf(q[l]) <= S[i]) p = max(p, q[l ++]); dec[i] = p; while (l <= r && S[q[r]] + suf(q[r]) >= S[i] + suf(i)) r --; q[++ r] = i; } __int128 ans = 0; int x = n; while (x) { ans += (__int128) suf(x) * suf(x); x = dec[x]; } print(ans); return 0; }
題目連接:Link。
部分分仍是有一些細講的價值的。
特殊性質:\(n \leq 2000\)。
暴力莽,暴力枚舉每一條邊的時間複雜度是 \(\mathcal{O(n)}\),暴力求重心的時間複雜度是 \(\mathcal{O(n)}\)。
暴力求重心你們應該都會吧 ...
時間複雜度 \(\mathcal{O(n^2)}\),這檔分仍是要吃掉的。
特殊性質:樹的形態爲一條鏈。
特殊性質:樹的形態爲滿二叉樹。
上圖是一棵滿二叉樹,咱們先對 \((u, v)\) 在滿二叉樹的右側時討論,左側同理。
咱們把樹分紅了 四個部分 和 一個點,設 \(v\) 的深度爲 \(p\),則各個部分的節點數爲:
首先,重心顯然不會出如今 \(\color{red}A\) 和 \(\color{yellow}B\) 裏,也並不可能出如今 \(\color{green}C\) 中除了根節點之外的節點中。
咱們對 \(2\) 號點(根的其中一個兒子)與根節點進行重點討論:
當 \(p < d\) 時,有 \(x < 2^{d - 1} - 1\),\(y = 2^{d - 1} - 1\)。\(x < y\),此時 \(2\) 號點是重心。
當 \(p = d\) 時,有 \(x = 2^{d - 1} - 1\),\(y = 2^{d - 1} - 1\)。\(x = y\),此時 \(2\) 號點與根節點都是重心。
設根節點爲 \(a\),根節點的左兒子爲 \(b\),根節點的右兒子爲 \(c\)。咱們發現,當 \((u, v)\) 在滿二叉樹的右側時,這條邊對答案有 \(b\) 的貢獻,當 \((u, v)\) 在滿二叉樹的左側時,這條邊對答案有 \(c\) 的貢獻,特別地,當 \((u, v)\) 中的 \(v\) 爲葉節點時,這條邊對答案還有額外的 \(a\) 的貢獻。
通過上述分析,故答案爲:
不難想到,能夠對於每一個點 \(u\),計算 \(u\) 成爲重心時,對答案的貢獻。
咱們欽定點 \(u\) 爲整棵樹的根,如今有一個 \(u\) 的子節點 \(v\),咱們要從 \(v\) 的子樹中再刪去一個大小爲 \(x\) 的小子樹,使得 \(u\) 成爲重心。
約定變量:
\(size_v\):以 \(u\) 爲根時,\(v\) 的子樹大小。
\(size'_v\):通過刪邊後的 \(v\) 的子樹大小,這裏實際上 \(size'_v = size_v - x\)。
\(s\):在 \(u\) 的全部子樹中,刨去 \(v\) 的子樹後的總節點數,這裏實際上 \(s = n - 1 - size_v\)。
\(m\):在 \(u\) 的全部子樹中,除 \(v\) 以外的最大子樹大小。
咱們來分析一下,通過刪邊後的 \(size'_v\) 的取值範圍:
通過上述分析,咱們能夠知道,通過刪邊後的 \(size'_v \in [2 \ast m - s - 1, s + 1]\)。
對於整棵樹的區間數點問題,咱們能夠預處理出前綴和 \(sum_i\) 表示 " 在整棵樹中,子樹大小在 \([1, i]\) 內的節點個數 ",從而轉化爲前綴作差的形式。
對於子樹內的區間數點問題,咱們能夠用順手處理的線段樹合併計算。
對於根節點到 \(u\) 點路徑上的區間數點問題,咱們能夠用一個樹狀數組實時維護根節點到 \(u\) 點的子樹大小,仍是能夠用前綴作差的形式計算。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; inline int read() { int x = 0, f = 1; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -f; s = getchar(); } while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); } return x * f; } const int N = 300100, M = 600100, MLOGN = 10000000; int n; int ovo, head[N], ver[M], Next[M]; void addedge(int u, int v) { ver[++ ovo] = v; Next[ovo] = head[u]; head[u] = ovo; } // BIT part int c[N]; void add(int x, int val) { for (; x <= n; x += x & -x) c[x] += val; } int calc(int x) { int ans = 0; for (; x; x -= x & -x) ans += c[x]; return ans; } // SegmentTree part int tot, root[N]; struct SegmentTree { int lc, rc; int cnt; } t[MLOGN]; int New() { tot ++; t[tot].lc = t[tot].rc = t[tot].cnt = 0; return tot; } void insert(int &p, int l, int r, int delta, int val) { if (!p) p = New(); t[p].cnt += val; if (l == r) return; int mid = (l + r) / 2; if (delta <= mid) insert(t[p].lc, l, mid, delta, val); else insert(t[p].rc, mid + 1, r, delta, val); } int merge(int p, int q) { if (!p || !q) return p ^ q; t[p].cnt += t[q].cnt; t[p].lc = merge(t[p].lc, t[q].lc); t[p].rc = merge(t[p].rc, t[q].rc); return p; } int ask(int p, int l, int r, int s, int e) { if (s <= l && r <= e) return t[p].cnt; int mid = (l + r) / 2; int val = 0; if (s <= mid) val += ask(t[p].lc, l, mid, s, e); if (mid < e) val += ask(t[p].rc, mid + 1, r, s, e); return val; } // solve part long long ans; int size[N]; void search(int u, int fa) { size[u] = 1; for (int i = head[u]; i; i = Next[i]) { int v = ver[i]; if (v == fa) continue; search(v, u); size[u] += size[v]; } } long long sum[N]; void dfs(int u, int fa) { int firv = 0, secv = 0; for (int i = head[u]; i; i = Next[i]) { int v = ver[i]; if (v == fa) continue; if (size[v] > firv) secv = firv, firv = size[v]; else if (size[v] > secv) secv = size[v]; } if (n - size[u] > firv) secv = firv, firv = n - size[u]; else if (n - size[u] > secv) secv = n - size[u]; for (int i = head[u]; i; i = Next[i]) { int v = ver[i]; if (v == fa) continue; add(size[v], 1), dfs(v, u), add(size[v], -1); int m = size[v] == firv ? secv : firv; int l = 2 * size[v] - n, r = n - 2 * m; if (l > n || r < 1 || l > r) { root[u] = merge(root[u], root[v]); continue; } if (l < 1) l = 1; if (r > n) r = n; ans += 1ll * ask(root[v], 1, n, l, r) * u; root[u] = merge(root[u], root[v]); } if (u == 1) return; int m = n - size[u] == firv ? secv : firv; int l = 2 * (n - size[u]) - n, r = n - 2 * m; if (l > n || r < 1 || l > r) { insert(root[u], 1, n, size[u], 1); return; } if (l < 1) l = 1; if (r > n) r = n; int cnt = 0; cnt += sum[r] - sum[l - 1]; cnt -= ask(root[u], 1, n, l, r); cnt -= calc(r) - calc(l - 1); l = n - l, r = n - r, swap(l, r); if (l < 1) l = 1; if (r > n) r = n; cnt += calc(r) - calc(l - 1); ans += 1ll * cnt * u; insert(root[u], 1, n, size[u], 1); } void work() { memset(head, 0, sizeof(head)); memset(c, 0, sizeof(c)); memset(sum, 0, sizeof(sum)); memset(root, 0, sizeof(root)); ovo = 0, tot = 0, ans = 0; n = read(); for (int i = 1; i < n; i ++) { int u = read(), v = read(); addedge(u, v), addedge(v, u); } search(1, 0); for (int i = 2; i <= n; i ++) sum[size[i]] ++; for (int i = 2; i <= n; i ++) sum[i] += sum[i - 1]; dfs(1, 0); printf("%lld\n", ans); } int main() { int T = read(); while (T --) work(); return 0; }
題目連接:Link。
咕咕咕。
我是不會告訴您其實我不會作這道題的 233。