inline ll read(){ ll x = 0, f = 0; char c = getchar(); while (c > 57 || c < 48) f |= c == 45, c = getchar(); while (c <= 57 && c >= 48) x = (x << 3) + (x << 1) + c - 48, c = getchar(); return f ? -x : x; }
voidmerge(int* a, int l, int mid, int r){ // 合并函數 std::vector<int> b; // 用 vector 申请一个辅助函數 int i = l, j = mid + 1; while (i <= mid && j <= r) { if (a[i] <= a[j]) b.push_back(a[i++]); // 按從小到大存放在 b 數組里面 else b.push_back(a[j++]), cnt += mid - i + 1; } while (i <= mid) b.push_back(a[i++]); // j 序列結束,將剩餘的 i 序列补充在 b 數組中 while (j <= r) b.push_back(a[j++]); // i 序列結束,將剩餘的 j 序列补充在 b 數組中 for (int i = l; i <= r; i++) a[i] = b[i - l];// 將 b 數組的值传遞给數組 a }
voidmergesort(int* a, int l, int r){ // 歸併排序 if (l < r) { int mid = (l + r) >> 1; mergesort(a, l, mid); // 對 a[l,mid] 进行排序 mergesort(a, mid + 1, r); // 對 a[mid+1,r] 进行排序 merge(a, l, mid, r); // 进行合并操作 } }
ll c[maxN][maxN]; voidgetC(int n, int p){ for (int i = 0; i <= n; ++i) { c[i][0] = 1; c[i][i] = 1; for (int j = 1; j < i; ++j) c[i][j] = (c[i-1][j-1] + c[i-1][j]) % p; } }
在時間複雜度Θ(n2) 內計算C00∼Cnn。
預處理階乘
1 2 3 4 5 6 7 8 9 10 11 12
ll fac[maxN], ifac[maxN];
voidinit(int n, int p){ fac[0] = 1; for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % p; ifac[n] = qpow(fac[n], p - 2, p); for (int i = n; i >= 1; i--) ifac[i - 1] = ifac[i] * i % p; }
ll C(int n, int m, int p){ return fac[n] * ifac[m] % p * ifac[n - m] % p; }
ll fact[maxN]; voiddofact(int n, int p){ fact[0] = fact[1] = 1; for (int i = 2; i < n; ++i) fact[i] = fact[i - 1] * i % p; }
ll inv[maxN]; ll doInv(ll a, ll p){ a %= p; if (a == 0) return-1; if (a == 1) return1; if (inv[a]) return inv[a]; return inv[a] = (p - p / a) * doInv(p % a, p) % p; }
ll C(ll n, ll m, ll p){ return n < m ? 0 : fact[n] * doInv(fact[m], p) % p * doInv(fact[n - m], p) % p; }
ll lucas(ll n, ll m, ll p){ return m == 0 ? 1 : lucas(n / p, m / p, p) * C(n % p, m % p, p) % p; }
時間複雜度Θ(p),要求p 比較小。
第十四講 位運算
1 2 3 4 5 6 7 8 9
-a = ~a + 1; // 取負數 if (a & 1); // 判斷奇偶 正奇數或負偶數爲 1 a >> (k-1) & 1; // 取 int 型變量的右數第 k 位 a = a | (1 << k-1); // 将 int 型變量的右數第 k 位置取 1 a = a << k | a >> 16-k; // int 型變量循環左移 k 次, 設 sizeof(int)=16 a = a >> k | a << 16-k; // int 型變量循環右移 k 次, 設 sizeof(int)=16 if ((!(a & (a - 1))) && a); // 判斷一个整數是不是 2 的幂 x ^= y; y ^= x; x ^= y; // 交换兩數 a & (-a); // 最低位的 1 以及它後边的 0 构成的數
std::sort(a, a + n); do { // doing } while (std::next_permutation(a, a + n);
第十六講 枚舉子集
時間複雜度Θ(2n),能解決的數据範圍在n⩽25。
1 2 3 4 5 6
for (int seq = 0; seq < (1ll << n); ++seq) { for (int i = 0; i < n; ++i) if (seq & (1ll << i)) { // doing } }
這是「二進制法」,此外有「增量構造法」「位向量法」。
分解和
1 2 3 4 5 6 7 8 9 10
int H[maxN]; voidHdfs(int n, int k, int Hmin, int Hcur){ if (n <= 0 && Hcur == k) { // doing } for (int i = Hmin; i <= n; ++i) { H[Hcur] = i; Hdfs(n - i, k, i, Hcur + 1); } }
std::deque<int> Q; // 存儲的是編號 for (int i = 0; i < n; ++i) { if (!Q.empty() && i - Q.front() >= m) Q.pop_front(); // 畢業 while (!Q.empty() && a[Q.back()] < a[i]) //(求區間最小值改成 > 即可) Q.pop_back(); // 比新生弱的當場退役 Q.push_back(i); // 新生入隊 if (i >= m - 1) printf("%d\n", a[Q.front()]); }
intdfs(int pos, int sum){ if (sum > m) return0; if (sum == m) return1; if (pos > n) return0; if (vis[pos][sum]) return vis[pos][sum]; // 搜過了就返回 int ans = 0; for (int i = 0; i <= a[pos]; ++i) ans = (ans + dfs(pos+1, sum+i)) % MOD; return vis[pos][sum] = ans; // 記錄當前狀態的結果 }
int f[maxN][maxN]; f[0][0] = 1; for (int i = 1; i <= n; ++i) for (int j = 0; j <= m; ++j) for (int k = 0; k <= min(j, a[i]); ++k) f[i][j] = (f[i][j] + f[i-1][j-k]) % MOD;
所求即是 f[n][m]。時間複雜度Θ(nmai),空間複雜度Θ(nm)。
思路四 滾動數組
1 2 3 4 5 6 7 8 9 10
int f[2][maxN], op = 0; f[0][0] = 1; for (int i = 1; i <= n; ++i) { op ^= 1; // 滾動 0101 for (int j = 0; j <= m; ++j) { f[op][j] = 0; // 清空(初始化) for (int k = 0; k <= min(j, a[i]); ++k) f[op][j] = (f[op][j] + f[op^1][j-k]) % MOD; } }
所求即是 f[n][m]。時間複雜度Θ(nmai),空間複雜度Θ(m)。
思路五 一維動態規劃(01 背包)
1 2 3 4 5 6
int f[maxn]; f[0] = 1; for (int i = 1; i <= n; ++i) for (int j = m; j >= 0; --j) for (int k = 1; k <= min(a[i], j); ++k) f[j] = (f[j] + f[j-k]) % MOD;
所求即是 f[m]。時間複雜度Θ(nmai)。
思路六 前缀和优化背包
继续观察方法五的代码,时间复杂度是Θ(n3) 级别的。与背包有一些差别的是这一句:
1 2
for (int k = 1; k <= min(a[i], j); ++k) f[j] = (f[j] + f[j-k]) % MOD;
int f[maxN], sum[maxN]; f[0] = 1; for (int i = 0; i <= m; ++i) sum[i] = 1; for (int i = 1; i <= n; ++i) { for (int j = m; j >= 1; --j) int t = j - min(a[i], j) - 1; if (t < 0) f[j] = (f[j] + sum[j-1]) % MOD; else f[j] = (f[j] + sum[j-1] - sum[t] + MOD) % MOD; } for(int j = 1; j <= m; ++j) sum[j] = (sum[j-1] + f[j]) % MOD; }
DP:最長上升子序列
方法一
狀態:dpi 表示以ai 結尾的最長上升子序列的長度;
狀轉方程:dpi=aj<aimin(dpj+1);
時間複雜度:Θ(n2)。
1 2 3 4 5 6 7 8 9
int ans = 0; for (int i = 0; i < n; ++i) { dp[i] = 1; for (int j = 0; j < i; ++j) { if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); } ans = max(ans, dp[i]); } printf("%d\n", ans);
方法二
1 2 3 4 5 6
vector<int> dp; dp.push_back(-1); for (int i = 0; i < n; ++i) { if (dp.back() < a[i]) dp.push_back(a[i]); else *std::lower_bound(dp.begin(), dp.end(), a[i]) = a[i]; } printf("%lld\n", dp.size() - 1);
由Dilworth 定理,要求不上升的子序列的最小个數,只需要反過來求最長上升子序列的長度。
DP:背包問題
01 背包
1 2 3 4 5 6
int V = read(), n = read(); for (int i = 1; i <= n; ++i) v[i] = read(), w[i] = read(); for (int i = 1; i <= n; ++i) // 物品 for (int j = V; j >= v[i]; --j) // 空間 dp[j] = max(dp[j], dp[j - v[i]] + w[i]); print(dp[V]);
記憶化搜索:
1 2 3 4 5 6 7 8 9 10 11 12 13
intdfs(int pos, int V){ if (mem[pos][V] != -1) return mem[pos][V]; if (pos == n + 1) return mem[pos][V] = 0; if (V >= v[pos]) return mem[pos][V] = max(dfs(pos + 1, V), dfs(pos + 1, V - v[pos]) + w[pos]); elsereturn mem[pos][V] = dfs(pos + 1, V); }
voideachT(){ memset(mem, -1, sizeof mem); int V = read(), n = read(); for (int i = 1; i <= n; ++i) v[i] = read(), w[i] = read(); print(dfs(1, V)); }
二維費用背包
1 2 3 4 5 6 7
int n = read(), V = read(), M = read(); for (int i = 1; i <= n; ++i) v[i] = read(), w[i] = read(), m[i] = read(); for (int i = 1; i <= n; ++i) // 物品 for (int j = V; j >= v[i]; --j) // 空間 for (int k = M; k >= m[i]; --k) // 重量 dp[j][k] = max(dp[j][k], dp[j - v[i]][k - m[i]] + w[i]); print(dp[V][M]);
分組背包
1 2 3 4 5 6 7 8 9 10 11
int V = read(), n = read(); for (int i = 1; i <= n; ++i) { int x = read(), y = read(), z = read(); v[z].push_back(x); w[z].push_back(y); } for (int i = 1; i <= n; ++i) // 物品 for (int j = V; j >= 1; --j) // 空間 for (int k = 0; k < v[i].size(); ++k) if (j >= v[i][k]) dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]); print(dp[V]);
int V = read(), N = read(); for (int i = 0; i < N; ++i) v[i] = read(), w[i] = read(), n[i] = read();
for (int i = 0; i < N; ++i) { if (n[i] > 1) { // 多重背包 int num = min(n[i], V / v[i]); // 可以放入的最大个數 int k = 1; // 當前拆分个數 放入 k 个物品 while (num > 0) { k = min(k, num); for (int j = V; j >= k * v[i]; --j) dp[j] = max(dp[j], dp[j - k * v[i]] + k * w[i]); num -= k; k <<= 1; }
constint maxN = 18; int map[maxN][maxN]; int ansx[maxN * maxN], ansy[maxN * maxN]; int m, n; int beginx, beginy, endx, endy; int cnt = 0; int dx[] = { 0, -1, 0, 1 }; int dy[] = { -1, 0, 1, 0 }; // 方向
voiddfs(int pos, int x, int y, int endx, int endy){ if (x < 1 || y < 1 || x > m || y > n || map[x][y] == 0) { return; } // 越界或是牆 if (x == endx && y == endy) { ++cnt; for (int i = 0; i < p; ++i) printf("(%d,%d)->", ansx[i], ansy[i]); printf("(%d,%d)\n", endx, endy); return; } // 終點 ansx[pos] = x; ansy[pos] = y; // 記錄答案 map[x][y] = 0; for (int i = 0; i < 4; ++i) dfs(pos + 1, x + dx[i], y + dy[i], endx, endy); map[x][y] = 1; }
voideachT(){ m = read(), n = read(); for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) map[i][j] = read(); beginx = read(), beginy = read(), endx = read(), endy = read(); if (map[endx][endy] == 0) { printf("-1\n"); return; } dfs(0, beginx, beginy, endx, endy); if (!cnt) printf("-1\n"); // 無解 }
constint maxN = 18; char map[maxN][maxN]; bool vis[maxN][maxN]; int m, n; bool flag = 0; int dx[] = { 0, 1, 0, -1 }; int dy[] = { 1, 0,-1, 0 }; // 方向
voiddfs(int x, int y){ if (flag) return; if (map[x][y] == '#' || vis[x][y]) return; // 是牆或走過了 if (x == m && y == n) { flag = 1; return; } // 有解 vis[x][y] = 1; for (int i = 0; i < 4; ++i) dfs(x + dx[i], y + dy[i]); }
voideachT(){ scanf("%d %d\n", &m, &n); for (int i = 1; i <= m; ++i) for (int j = 1; j <= n + 1; ++j) scanf("%c", &map[i][j]); for (int i = 1; i <= m; ++i) map[i][0] = map[i][n + 1] = '#'; for (int j = 1; j <= n; ++j) map[0][j] = map[m + 1][j] = '#'; // 邊界 dfs(1, 1); printf("%s\n", flag ? "Yes" : "No"); }
int n, m; int map[maxN][maxN]; // 繪製地圖 bool vis[maxN][maxN]; // 標記是否走過 int steps[maxN][maxN]; // 記錄走到某一点的步數 pii ans[maxN][maxN]; // 記錄最短路徑 std::queue<pii> path; // 存儲當前處理的值
// 處理當前目標 for (int i = 0; i < N; ++i) { int x = t.first + dx[i]; // 下一步的坐標 int y = t.second + dy[i]; if (!vis[x][y] && !map[x][y]) { // 如果沒走過 vis[x][y] = 1; // 走過了 path.push({ x,y }); // BFS 入隊 steps[x][y] = steps[t.first][t.second] + 1; // 記錄步數 ans[x][y].first = t.first, ans[x][y].second = t.second; // 記錄最短路徑 } } } // 輸出走到每一格点的最小步數 for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) printf("%-5d", steps[i][j]); printf("\n"); }
// 輸出到終点的最小步數 printf("%d", steps[xn][yn]); }
voideachT(){ // 輸入 scanf("%d %d", &n, &m); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) scanf("%d", &map[i][j]); // 邊界處理 for (int i = 1; i <= n; ++i) map[i][0] = map[i][m + 1] = 0; for (int j = 1; j <= m; ++j) map[0][j] = map[n + 1][j] = 0; // BFS 的起点 bfs(n, m, n, m, 1, 1); }
intmain(){ eachT(); return0; }
第二十三講 數論
取餘快讀
1 2 3 4 5 6 7 8 9
inlineintreadmod(){ int c = getchar(), x = 0; while (c > 57 || c < 48 && c != EOF) c = getchar(); while (c <= 57 && c >= 48) { x = ((x << 3) + (x << 1) + c - 48) % MOD; c = getchar(); } return x; } // 使用:int a = readmod();
ll exgcd(ll a, ll b, ll& x, ll& y){ if (b == 0) { x = 1; y = 0; return a; } ll d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d; }
ll liEq(ll a, ll b, ll c){ // a x + b y = c ll d = gcd(a, b); if (c % d) return-1; // 無解 ll x, y; a /= d, b /= d, c /= d; exgcd(a, b, x, y); return (x * c % b) + b * d) % b; } // 同餘方程 ax == c (mod b) 的最小正整數解 // 即解不定方程 ax + by = c // 調用 liEq(a,b,c);
一個數的數論倒數a−1(modp)
首先注意a 必須與p 互素,否則數論倒數不存在。
若p 是素數:
1 2 3
intinv(int n, int p){ returnqpow(n, p - 2); } // a_{-1} mod p (p 是素數)
一般情況:
1 2 3
intinv(int n, int p){ returnliEq(a, p, 1); } // a_{-1} mod p (p 無限制)
一系列數的數論倒數
1 2 3 4 5 6 7 8
ll inv[maxN]; ll doInv(ll a, ll p){ a %= p; if (a == 0) return-1; // 不存在 if (a == 1) return1; if (inv[a]) return inv[a]; return inv[a] = (p - p / a) * doInv(p % a, p) % p; } // a_{-1} mod p (p 無限制)
從1 到n 的連續自然數的數論倒數(n<p∧p 是素數):
1 2 3 4 5 6 7
ll inv[maxN]; inlinevoiddoinv(ll n, int p){ inv[1] = 1; for (int i = 2; i <= n; ++i) { inv[i] = (p - p / i) * inv[p % i] % p; } }
素數篩(歐拉篩)
1 2 3 4 5 6 7 8 9 10 11 12 13
bool isntPrime[maxN]; std::vector<int> prime; voiddoPrime(int mx){ for (int i = 2; i <= mx; ++i) { if (!isntPrime[i]) prime.push_back(i); for (auto p : prime) { if (p * i > mx) break; isntPrime[p * i] = 1; if (i % p == 0) break; } } isntPrime[1] = 1; }
使用:
1 2 3 4
doPrime(); // 預處理 if (!isntPrime[n]) { // n 是素數 }
計算約數個數 & 約數之和
1 2 3 4 5 6 7 8 9 10 11 12 13 14
intnumOfDivisors(int x){ int ans = 1; for (int i = 2; i * i <= x; ++i) { int r = 0; while (x % i == 0) ++r, x /= i; if (r) ans *= r + 1; } if (x > 1) ans *= 2; return ans; } // x 的約數個數
intmod(int a, int b){ if (a % b == 0) return0; elseif (a >= 0) return a % b; elseif (b >= 0) return a % b + b; elsereturn a % b - b; } // return a mod b; (maths)
intto10(string s, int r){ int num = 0; ll pow = 1; for (int i = s.size() - 1; i >= 0; --i) { int a = (s[i] >= 'A') ? (10 + s[i] - 'A') : (s[i] - '0'); num += a * pow; pow *= r; } return num; }
string from10(int num, int r){ if (num == 0) return"0"; string ans; while (num) { int remainder = mod(num, r); ans = remainder < 10 ? char('0' + remainder) + ans : char('A' + remainder - 10) + ans; num = (num - remainder) / r; } return ans; }