voidquickSort(int a[], int l, int r){ if (l >= r) return; int i = l - 1, j = r + 1, x = a[l+r>>1]; while (i < j) { do i++; while (a[i] < x); do j--; while (a[j] > x); if (i < j) swap(a[i], a[j]); } quickSort(a, l, j); quickSort(a, j+1, r); }
voidmergeSort(int l, int r){ if (l >= r) return; int mid = l + r >> 1; mergeSort(l, mid); mergeSort(mid + 1, r); int i = l, j = mid + 1, k = 0; while (i <= mid && j <= r) { if (a[i] <= a[j]) temp[k++] = a[i++]; else temp[k++] = a[j++]; } while (i <= mid) temp[k++] = a[i++]; while (j <= r) temp[k++] = a[j++]; for (i = l, k = 0; i <= r; i++, k++) a[i] = temp[k]; }
intmain(){ scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); mergeSort(0, n - 1); for (int i = 0; i < n; i++) printf("%d ", a[i]); return0; }
intmain(){ scanf("%d%d", &n, &q); for (int i = 0; i < n; i++) scanf("%d", &a[i]); while (q--) { int x; scanf("%d", &x); int l = 0, r = n - 1; while (l < r) { int mid = l + r >> 1; if (a[mid] >= x) r = mid; else l = mid + 1; } if (a[l] != x) printf("-1 -1\n"); else { printf("%d ", l); int r = n - 1; while (l < r) { int mid = l + r + 1 >> 1; if (a[mid] <= x) l = mid; else r = mid - 1; } printf("%d\n", l); } } return0; }
#include<iostream> #include<vector> usingnamespace std; vector<int> mul(vector<int>& A, int b){ vector<int> res; int t = 0; for (int i = 0; i < A.size() || t; i++) { if (i < A.size()) t += b * A[i]; res.push_back(t % 10); t /= 10; } while (res.size() > 1 && res.back() == 0) res.pop_back(); return res; } intmain(){ string s; int b; vector<int> A, res; cin >> s >> b; for (int i = s.length() - 1; i >= 0; i--) A.push_back(s[i] - '0'); res = mul(A, b); for (int i = res.size() - 1; i >= 0; i--) cout << res[i]; return0; }
#include<iostream> #include<vector> #include<algorithm> usingnamespace std; vector<int> div(vector<int> &A, int b, int &r){ r = 0; vector<int> res; for (int i = A.size() - 1; i >= 0; i--) { r = r * 10 + A[i]; res.push_back(r / b); r %= b; } reverse(res.begin(), res.end()); while (res.size() > 1 && res.back() == 0) res.pop_back(); return res; } intmain(){ string s; int b, r; vector<int> A, res; cin >> s >> b; for (int i = s.length() - 1; i >= 0; i--) A.push_back(s[i] - '0'); res = div(A, b, r); for (int i = res.size() - 1; i >= 0; i--) cout << res[i]; cout << endl << r << endl; return0; }
1.5 前缀和
1.5.1 一维前缀和
输入一个长度为 n 的整数序列。接下来再输入 m 个询问,每个询问输入一对 l, r。对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。n,m可能比较大。
适用于需要大量计算序列中某一部分和的情况。
维护一个 s数组,其中s[i]表示的含义是前 i 个数据的和。求第 l 到第 r 个数的和时,只需要S[r] - s[l - 1]即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include<iostream> usingnamespace std; constint maxn = 1e5 + 10; int a[maxn], s[maxn]; intmain(){ int n, m, l, r; scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); s[i] = s[i-1] + a[i]; } for (int i = 0; i < m; i++) { scanf("%d%d", &l, &r); int res = s[r] - s[l-1]; printf("%d\n", res); } return0; }
1.5.2 二维前缀和
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1, y1, x2, y2 表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
#include<iostream> #include<vector> #include<algorithm> usingnamespace std; typedef pair<int, int> PII; constint N = 3e5 + 10; int n, m; int a[N], s[N]; //分别存储离散化后的下标对应数和前缀和 vector<int> alls; //离散之前的所有下标 vector<PII> add, query; //表示添加和查询 intfind(int x){ int l = 0, r = alls.size() - 1; while (l < r) { int mid = l + r >> 1; if (alls[mid] >= x) r = mid; else l = mid + 1; } return r + 1; } intmain(){ scanf("%d%d", &n, &m); for (int i = 0; i < n; i++) { int x, c; scanf("%d%d", &x, &c); alls.push_back(x); add.push_back({x, c}); } for (int i = 0; i < m; i++) { int l, r; scanf("%d%d", &l, &r); alls.push_back(l); alls.push_back(r); query.push_back({l, r}); } sort(alls.begin(), alls.end()); alls.erase(unique(alls.begin(), alls.end()), alls.end()); for (auto item : add) { int x = find(item.first); a[x] += item.second; } for (int i = 1; i <= alls.size(); i++) { s[i] = s[i-1] + a[i]; } for (auto item : query) { int l = find(item.first); int r = find(item.second); int res = s[r] - s[l-1]; printf("%d\n", res); } return0; }
intmain(){ int n; scanf("%d", &n); vector<pii> v; for (int i = 0; i < n ; i++) { int l, r; scanf("%d%d", &l, &r); v.push_back({l, r}); } sort(v.begin(), v.end()); int res = n; for (int i = 1; i < n; i++) { if (v[i].first <= v[i-1].second) { res--; v[i].second = max(v[i].second, v[i-1].second); } } cout << res; return0; }
constint N = 1e6 + 10; int a[N]; deque<int> minq, maxq;
intmain(){ int n, k; cin >> n >> k; for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) { if (!minq.empty() && minq.front() < i - k + 1) minq.pop_front(); while (!minq.empty() && a[i] <= a[minq.back()]) minq.pop_back(); minq.push_back(i); if (i >= k - 1) printf("%d ", a[minq.front()]); } cout << endl; for (int i = 0; i < n; i++) { if (!maxq.empty() && maxq.front() < i - k + 1) maxq.pop_front(); while (!maxq.empty() && a[i] >= a[maxq.back()]) maxq.pop_back(); maxq.push_back(i); if (i >= k - 1) printf("%d ", a[maxq.front()]); } return0; }
intfind(int a){ // 路径压缩 int x = a; while (a != father[a]) a = father[a]; while (x != father[x]) { int z = x; x = father[x]; father[x] = a; } return a; }
voidmerge(int a, int b){ int fa = find(a), fb = find(b); if (fa != fb) father[fa] = fb; }
intmain(){ cin >> n >> m; for (int i = 1; i <= n; i++) { father[i] = i; } while (m--) { char c; int a, b; cin >> c >> a >> b; if (c == 'M') merge(a, b); else { if (find(a) == find(b)) cout << "Yes" << endl; else cout << "No" << endl; } } return0; }
constint N = 1e5 + 10; int son[N][26], cnt[N], idx; // son[1][0] = 2表示第一条子树的a下一个节点为2
voidinsert(string s){ int p = 0; for (int i = 0; i < s.size(); i++) { int u = s[i] - 'a'; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } cnt[p]++; }
intquery(string s){ int p = 0; for (int i = 0; i < s.size(); i++) { int u = s[i] - 'a'; if (!son[p][u]) return0; p = son[p][u]; } return cnt[p]; }
intmain(){ int n; cin >> n; while (n--) { string c, s; cin >> c >> s; if (c == "I") insert(s); else cout << query(s) << endl; } return0; }
boolcmp(pii a, pii b){ return a.second < b.second; }
intmain(){ int n, res = 1; cin >> n; vector<pii> vp; for (int i = 0; i < n; i++) { int x, y; scanf("%d%d", &x, &y); vp.push_back({x, y}); } sort(vp.begin(), vp.end(), cmp); int end = vp[0].second; for (int i = 1; i < n; i++) { if (end < vp[i].first) { res++; end = vp[i].second; } } cout << res;
boolcmp(pii a, pii b){ return a.first < b.first; }
intmain(){ int st, ed, n, res = 0; scanf("%d%d%d", &st, &ed, &n); for (int i = 0; i < n; i++) { int x, y; scanf("%d%d", &x, &y); vp.push_back({x, y}); } sort(vp.begin(), vp.end(), cmp); bool flag = false; for (int i = 0; i < n; i++) { int j = i, r = -2e9; while (j < n && vp[j].first <= st) { r = max(r, vp[j].second); j++; } if (r < st) { // 出现断层或者所有区间小于st flag = false; break; } res++; if (r >= ed) { flag = true; break; } i = j - 1; // 那些end没有vp[j]大的都无用 st = r; } if (!flag) cout << -1 << endl; else cout << res << endl; return0; }
N只奶牛决定叠罗汉。表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
intmain(){ int n; cin >> n; for (int i = 0; i < n; i++) { int w, s; cin >> w >> s; v.push_back({w + s, s}); } sort(v.begin(), v.end()); int res = -2e9, sum = 0; for (int i = 0; i < n; i++) { int s = v[i].second, w = v[i].first - s; res = max(res, sum - s); sum += w; } cout << res << endl; return0; }
// maxSumSqu为最大平方和 int n, k, x, maxSumSqu = -1, a[maxn]; // temp存放临时方案,ans存放平方和最大的方案 vector<int> temp, ans;
voidDFS(int index, int tempK, int sum, int sumSqu){ if (tempK = k && sum == x) { if (sumSqu > maxSumSqu) { maxSumSqu = sumSqu; ans = temp; } return; } if (tempK > k || index == n || sum > x) return; temp.push_back(a[index]); DFS(index + 1, tempK + 1, sum + a[index], sumSqu + a[index] * a[index]); temp.pop_back(); DFS(index + 1, tempK, sum, sumSqu); }
structNode { int x, y; int step; // 表示步长 } s, t, node;
int n, m; char maze[maxn][maxn]; bool inq[maxn][maxn] = {false}; int x[4] = {0, 0, 1, -1}; int y[4] = {1, -1, 0, 0};
// 检测位置是否有效 booltest(int x, int y){ if (x >= n || x < 0 || y >= m || y < 0) returnfalse; if (maze[x][y] == ‘*’) returnfalse; if (inq[x][y] == true) returnfalse; returntrue; }
intBFS(){ queue<Node> q; q.push(s); while (!q.empty()) { Node top = q.front(); q.pop(); if (top.x = t.x && top.y == t.y) return top.step; for (int i = 0; i < 4; i++) { int newx = top.x + x[i]; int newy = top.y + y[i]; if (test(newx, newy)) { node.x = newx; node.y = newy; node.step = top.step + 1; q.push(node); inq[newx][newy] = true; } } return-1; } }
intmain(){ scanf(“%d%d”, &n, &m); for (int i = 0; i < n; i++) { getchar(); for (int j = 0; j < m; j++) { maze[x][y] = getchar(); } maze[x][m+1] = ‘\0’; } scanf(“%d%d%d%d”, &s.x, &s.y, &t.x, &t.y); s.step = 0; printf(“%d”, BFS()); return0; }
constint N = 1e5 + 10; int n, m, d[N]; vector<int> v[N], res; queue<int> q;
booltopoSort(){ for (int i = 1; i <= n; i++) { if (d[i] == 0) { q.push(i); } } while (!q.empty()) { int u = q.front(); q.pop(); res.push_back(u); for (int i = 0; i < v[u].size(); i++) { int j = v[u][i]; d[j]--; if (d[j] == 0) { q.push(j); } } } if (res.size() == n) returntrue; elsereturnfalse; }
intmain(){ scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) { int a, b; scanf("%d%d", &a, &b); v[a].push_back(b); d[b]++; } if (topoSort()) { for (int i = 0; i < n; i++) { cout << res[i] << " "; } } else cout << -1;
constint N = 1e5 + 10, INF = 0x3fffffff; int n, m, d[N]; vector<pii> g[N]; bool vis[N];
voidDijkstra(int s){ fill(d, d + N, INF); d[s] = 0; for (int i = 0; i < n; i++) { int u = -1, minn = INF; for (int j = 0; j < n; j++) { if (!vis[j] && minn > d[j]) { minn = d[j]; u = j; } } if (u == -1) return; vis[u] = true; for (int i = 0; i < g[u].size(); i++) { int v = g[u][i].first; int dis = g[u][i].second; if (!vis[v] && d[v] > d[u] + dis) { d[v] = d[u] + dis; } } } }
intmain(){ scanf("%d%d", &n, &m); while (m--) { int a, v ,dis; scanf("%d%d%d", &a, &v, &dis); g[a].push_back({v, dis}); } Dijkstra(1); int res = d[n] == INF ? -1 : d[n]; printf("%d", res);
constint N = 1e5 + 10, INF = 0x3fffffff; int n, m, d[N]; vector<pii> g[N]; priority_queue<pii, vector<pii>, greater<pii>> heap; bool vis[N];
voidDijkstra_Heap(int s){ fill(d, d + N, INF); d[s] = 0; heap.push({0, s}); while (!heap.empty()) { pii p = heap.top(); heap.pop(); int u = p.second; if (vis[u]) continue; vis[u] = true; for (int i = 0; i < g[u].size(); i++) { int v = g[u][i].first; int dis = g[u][i].second; if (!vis[v] && d[v] > d[u] + dis) { d[v] = d[u] + dis; heap.push({d[v], v}); } } } }
intmain(){ scanf("%d%d", &n, &m); while (m--) { int a, v, dis; scanf("%d%d%d", &a, &v, &dis); g[a].push_back({v, dis}); } Dijkstra_Heap(1); int res = d[n] == INF ? -1 : d[n]; printf("%d", res); return0; }
constint N = 510, M = 1e4 + 10, INF = 0x3fffffff; int n, m, k; int d[N], backup[N]; vector<pii> g[M];
intBellman(int s){ memset(d, 0x3f3f3f3f, sizeof d); d[s] = 0; for (int i = 0; i < k; i++) { memcpy(backup, d, sizeof d); for (int u = 1; u <= n; u++) { for (int j = 0; j < g[u].size(); j++) { int v = g[u][j].first; int dis = g[u][j].second; d[v] = min(d[v], backup[u] + dis); } } } if (d[n] > 0x3f3f3f3f / 2) return-1; elsereturn d[n]; }
intmain(){ scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < m; i++) { int a, v, dis; scanf("%d%d%d", &a, &v, &dis); g[a].push_back({v, dis}); } int t = Bellman(1); if (t == -1) cout << "impossible"; else cout << t;
constint N = 1e5 + 10, INF = 0x3f3f3f3f; int n, m, d[N]; vector<pii> g[N]; bool st[N]; queue<int> q;
intspfa(int s){ fill(d, d + N, INF); d[s] = 0; q.push(s); st[s] = true; while (!q.empty()) { int u = q.front(); q.pop(); st[u] = false; for (int i = 0; i < g[u].size(); i++) { int v = g[u][i].first; int dis = g[u][i].second; if (d[v] > d[u] + dis) { d[v] = d[u] + dis; if (!st[v]) { q.push(v); st[v] = true; } } } } if (d[n] == INF) return-1; elsereturn d[n]; }
intmain(){ scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) { int a, v, dis; scanf("%d%d%d", &a, &v, &dis); g[a].push_back({v, dis}); } int t = spfa(1); if (t == -1) cout << "impossible" << endl; else cout << t << endl;
constint N = 1e5 + 10, INF = 0x3f3f3f3f; int n, m, d[N], cnt[N]; vector<pii> g[N]; bool st[N]; queue<int> q;
boolspfa(){ fill(d, d + N, INF); for (int i = 1; i <= n; i++) { q.push(i); st[i] = true; } while (!q.empty()) { int u = q.front(); q.pop(); st[u] = false; for (int i = 0; i < g[u].size(); i++) { int v = g[u][i].first; int dis = g[u][i].second; if (d[v] > d[u] + dis) { cnt[v] = cnt[u] + 1; if (cnt[v] >= n) returntrue; d[v] = d[u] + dis; if (!st[v]) { q.push(v); st[v] = true; } } } } returnfalse; }
intmain(){ scanf("%d%d", &n, &m); for (int i = 0; i < m; i++) { int a, v, dis; scanf("%d%d%d", &a, &v, &dis); g[a].push_back({v, dis}); } if (spfa()) cout << "Yes" << endl; else cout << "No" << endl;
constint N = 510, INF = 0x3f3f3f3f; int n, m, d[N]; vector<PII> g[N]; bool vis[N];
intprim(){ int res = 0; fill(d, d + N, INF); d[1] = 0; for (int i = 0; i < n; i++) { int u = -1, minn = INF; for (int j = 1; j <= n; j++) { if (!vis[j] && d[j] < minn) { minn = d[j]; u = j; } } vis[u] = true; if (u == -1) return INF; res += d[u]; for (int j = 0; j < g[u].size(); j++) { int v = g[u][j].first; int dis = g[u][j].second; if (!vis[v] && d[v] > dis) { //唯一不同的地方 d[v] = dis; } } } return res; }
intmain(){ cin >> n >> m; while (m--) { int a, b, dis; cin >> a >> b >> dis; g[a].push_back({b, dis}); g[b].push_back({a, dis}); } int t = prim(); if (t == INF) cout << "impossible" << endl; else cout << t << endl; return0; }
constint N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f; int n, m; int father[N]; structedge { int a, b, dis; } e[M];
boolcmp(edge a, edge b){ return a.dis < b.dis; }
intfind(int a){ int x = a; while (a != father[a]) { a = father[a]; } while (x != father[x]) { x = father[x]; father[x] = a; } return a; }
intkruskal(){ sort(e, e + m, cmp); for (int i = 1; i <= n; i++) { father[i] = i; } int res = 0, num = 0; for (int i = 0; i < m; i++) { int fa = find(e[i].a), fb = find(e[i].b); if (fa != fb) { father[fa] = fb; res += e[i].dis; num++; if (num == n - 1) break; } } if (num < n - 1) return INF; elsereturn res; }
intmain(){ cin >> n >> m; for (int i = 0; i < m; i++) { int a, b, dis; cin >> a >> b >> dis; e[i] = {a, b, dis}; } int res = kruskal(); if (res == INF) cout << "impossible" << endl; else cout << res << endl; return0; }
voiddivide(int x){ for (int i = 2; i <= x / i; i++) { if (x % i == 0) { int s = 0; while (x % i == 0) { x /= i; s++; } printf("%d %d\n", i, s); } } if (x > 1) printf("%d %d\n", x, 1); printf("\n"); }
intmain(){ int n; cin >> n; while (n--) { int a; vector<int> v; cin >> a; for (int i = 1; i <= a / i; i++) { if (a % i == 0) { v.push_back(i); v.push_back(a / i); if (i == a / i) v.pop_back(); } } sort(v.begin(), v.end()); for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } cout << endl; } return0; }
7.2.2 约数的个数
给定n个正整数ai,请你输出这些数的乘积的约数的个数,答案对1e9+7取模。
用哈希表存储质因数的个数,res * (prime.second + 1)过程可能爆 int 所以要用 long long。
intmain(){ int n; cin >> n; while (n--) { int x; cin >> x; for (int i = 2; i <= x / i; i++) { if (x % i == 0) { while (x % i == 0) { x /= i; primes[i]++; } } } if (x > 1) primes[x]++; } LL res = 1; for (auto prime : primes) { res = res * (prime.second + 1) % mod; } cout << res << endl; return0; }
intmain(){ int n; cin >> n; while (n--) { //求质因数 int c; cin >> c; for (int i = 2; i <= c / i; i++) { if (c % i == 0) { while (c % i == 0) { c /= i; primes[i]++; } } } if (c > 1) primes[c]++; } LL res = 1; for (auto prime : primes) { //套公式 int p = prime.first, mi = prime.second; LL t = 1; while (mi--) { t = (t * p + 1) % mod; } res = res * t % mod; } cout << res << endl; return0; }
7.2.4 最大公约数 & 最小公倍数
我们将使用辗转相除法来求解两个数 a 和 b 的最大公约数。
首先给出一个定理:0 和一个整数 a 的最大公约数是 a(不是0)。
然后给出一个等式gcd(a, b) = gcd(b, a % b),其中 gcd 表示两个数的最大公约数,下面证明这个等式:
将 a 写作 a = kb + r,其中 k 为 a 整除 b 得到的商,r 为余数。设 d 为 a 和 b 的任意一个约数。r = a - kb,因此毫无疑问 d 也是 r 的一个约数。也就是说,a 和 b 的任意一个约数,也是 b 和 a % b 的一个约数。同理可以得到 b 和 a % b 的任意一个约数,也是a 和 b 的一个约数。当集合 A 包括集合 B,集合 B 包括集合 A 时,可以得到两个集合相等,因此 a 和 b 的最大公约数,也一定是 b 和 a % b 的最大公约数。
观察等式可以发现如果 a < b 的话,等式会将 a 和 b 交换。
辗转相除法求最大公约数下降得非常快,是级别的下降速度。
最小公倍数为 a * b / d,可以从集合的角度来理解,a 和 b 的最小公倍数为二者相乘除以最大重叠的那一部分,即为最大公约数。实际中为了防溢出常写成 a / d * b。
1 2 3 4
intgcd(int a, int b){ if (b == 0) return a; elsereturngcd(b, a % b); }
可能有一些人有疑问了,虽然上面的两个式子不成立,我为什么不先计算a / b的值然后取模呢?是的,大部分情况下先计算 a / b 都是没有问题的,但是考虑这种情况:计算数值巨大的组合数, 对 1e9 + 7 取模的值,这个数的分子和分母都十分庞大。在每一步的运算过程中都应该对 1e9 + 7 取模,否则甚至会爆long long。所以将除法取模转化为乘法取模十分有必要。
一个数 b 存在逆元的充要条件是 b 与模数 m 互质。特别的,如果 m 是素数,且 a 不是 m 的倍数,则 a 模 m 的逆元为。这个公式可以由费马小定理推导而来。
费马小定理的公式:。
7.4 拓展欧几里得
欧几里得算法是我们之前辗转相除求最大公约数的算法。而拓展欧几里得所要解决的一个问题是:给定两个非零整数 a 和 b ,求一组整数解 (x, y),使得 ax + by = gcd(a, b) 成立。其中gcd为最大公约数。
理解拓展欧几里得的核心在于理解gcd(a, b) = gcd(b, a % b)这个公式。这个公式在之前欧几里得算法求最大公约数那一节介绍过了。
我们知道在欧几里得算法递归结束时能得到一个式子a * 1 + b * 0 = gcd(a, b),这是否就可以理解为 x 和 y 在递归的过程中最后变化成了 1 和 0 呢?是的。我们希望能通过gcd(a, b) = gcd(b, a % b)这个公式,逆推出 x 和 y 初始的值。我们用 x1 和 y1 表示递归上层的 x 和 y 的值,用 x2 和 y2 表示下层的 x 和 y 的值,下面是推导过程:
当计算 gcd(a, b) 时,有 a * x1 + b * y1 = gcd 成立,在下一步计算 gcd(b, a % b) 中,有 b * x2 + (a % b) * y2 = gcd 成立。于是得到 a * x1 + b * y1 = b * x2 + (a % b) * y2。而 a % b = a - (a / b) * b,所以最后的式子可以写成 a * x1 + b * y1 = a * y2 + b * (x2 - (a / b) * y2)。对比左右两边很快可以得到下面的递推公式:
于是我们就得到了递推公式。细心的你通过上面的证明可能发现了,ax + by = gcd(a, b) 不知有一组解。是的,我们通过拓展欧几里得求出来的其中的一组解。
观察公式我们可以发现一个有趣的现象:如果在传递 x 和 y 时翻转,那么就不用多余的代码更新 x 的值了,这对于简化代码有一些帮助。同时因为我们希望保留低层递归对 x 和 y 的修改,所以需要传引用。
1 2 3 4 5 6 7 8 9
intexGcd(int a, int b, int &x, int &y){ if (b == 0) { x = 1, y = 0; return a; } int d = exGcd(b, a % b, y, x); y -= a / b * x; return d; }
如果不翻转x和y的话,那么拓展欧几里得的代码为:
1 2 3 4 5 6 7 8 9 10 11
intexGcd(int a, int b, int &x, int &y){ if (b == 0) { x = 1, y = 0; return a; } int d = exGcd(b, a % b, x, y); int temp = x; x = y; y = temp - a / b * y; return d; }
7.5 组合数
一个组合数,我们会根据 a 和 b 的取值范围的不同来选取不同的方法计算,保证不超时。组合数很大, 所以一般来说都会要求组合数对一个数取模的值。在接下来的讲解中,这个取模的数都是素数。
7.5.1 组合数(递推公式)
。适用于 n 与 m 较小的情况(当 n 和 m 超过 10000 时就很危险了)。时间复杂度是。递推公式的好处是求出了一张表保存了所有要求的组合数,所以适用于多次查询的情况。
typedeflonglong LL; constint mod = 1e9 + 7; constint N = 1e5 + 10; int fact[N], infact[N]; //表示阶乘和阶乘的逆元
LL quickMi(int a, int k, int p){ LL res = 1; while (k) { if (k & 1) res = res * a % p; k >>= 1; a = (LL)a * a % p; } return res; }
voidgetFact(){ fact[0] = infact[0] = 1; for (int i = 1; i < N; i++) { fact[i] = (LL)fact[i-1] * i % mod; infact[i] = (LL)infact[i-1] * quickMi(i, mod - 2, mod) % mod; } }
intmain(){ int n; scanf("%d", &n); getFact(); while (n--) { int a, b; scanf("%d%d", &a, &b); int res = (LL)fact[a] * infact[a-b] % mod * infact[b] % mod; printf("%d\n", res); } return0; }
7.5.3 组合数(Lucas)
我们来考虑的极端情况。如此庞大的数字
首先介绍我们的解决方案,Lucas定理:
如果 p 是素数,将 m 和 n 表示为 p 进制: 那么 Lucas 定理告诉我们:
举个例子来说,对于来说,m = 3, n = 8,将 m 和 n 表示成五进制形式: 于是有。
这意味着我们可以在组合数的表示中将 n 和 m 两个十分庞大的数分解为多个小组合数相乘,其中 a 和 b 都小于 p。当 时都在我们的求解范围内,唯一的要求就是 p 要为素数。
intquickMi(int a, int b){ int res = 1; while (b) { if (b & 1) res = (LL)res * a % p; b >>= 1; a = (LL)a * a % p; } return res; }
intC(int a, int b){ int res = 1; for (int i = 1, j = a; i <= b; i++, j--) { res = (LL)res * j % p; res = (LL)res * quickMi(i, p - 2) % p; } return res; }
intLucas(LL a, LL b){ if (a < p && b < p) returnC(a, b); elsereturn (LL)C(a % p, b % p) * Lucas(a / p, b / p) % p; }
intmain(){ int n; cin >> n; while (n--) { LL a, b; cin >> a >> b >> p; cout << Lucas(a, b) << endl;; } return0; }
intmain(){ int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> v[i] >> w[i]; } for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { dp[i][j] = dp[i-1][j]; if (j >= v[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]); } } cout << dp[n][m] << endl; return0; }
intmain(){ int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> v[i] >> w[i] >> s[i]; } for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { for (int k = 0; k <= s[i] && k * v[i] <= j; k++) { dp[i][j] = max(dp[i][j], dp[i-1][j - v[i] * k] + w[i] * k); } } } cout << dp[n][m] << endl; return0; }
intmain(){ int n, m, cnt = 0; // cnt表示转化后的总物品数 cin >> n >> m; for (int i = 0; i < n; i++) { int a, b, s; cin >> a >> b >> s; int k = 1; // k为二进制倍数 while (k <= s) { cnt++; v[cnt] = k * a; w[cnt] = k * b; s -= k; k *= 2; } if (s > 0) { cnt++; v[cnt] = s * a; w[cnt] = s * b; } } n = cnt; for (int i = 1; i <= n; i++) { for (int j = m; j >= v[i]; j--) { dp[j] = max(dp[j], dp[j - v[i]] + w[i]); } } cout << dp[m] << endl; return0; }
8.1.5 分组背包问题
有N组物品和容量为V的背包,每组物品有若干个,同一组物品最多选一个,求如何选取总价值最大。
多重背包问题是枚举第 i 个物品选多少个,分组背包问题是枚举第 i 组物品选哪一个。二者的代码有一定相似之处。
constint N = 110; int dp[N], s[N], v[N][N], w[N][N];
intmain(){ int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> s[i]; for (int k = 1; k <= s[i]; k++) cin >> v[i][k] >> w[i][k]; } for (int i = 1; i <= n; i++) for (int j = m; j >= 0; j--) for (int k = 1; k <= s[i]; k++) if (v[i][k] <= j) dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]); cout << dp[m] << endl; return0; }