memset also blocks time
\((x\mod a) \mod ba = (x \mod ba )\mod a\)
Find out how many numbers in \ (1 ~ n \) are multiples of \ (i \), that is \ (n / i \)
Array non global variable to initialize
Push more
Learn to quote complex variables appropriately
STL
vector, Variable length array, multiplication idea size() Returns the number of elements empty() Returns whether it is empty clear() empty front()/back() push_back()/pop_back() begin()/end() [] Support comparison operation, in dictionary order pair<int, int> first, First element second, Second element Support comparison operation to first Is the first keyword, with second Is the second keyword (dictionary order) string,character string size()/length() Return string length empty() clear() substr(Starting subscript,(Substring length)) Return substring c_str() Returns the starting address of the character array where the string is located queue, queue size() empty() push() Insert an element at the end of the queue front() Return queue header element back() Return tail element pop() Pop up queue header element priority_queue, Priority queue. The default is large root heap size() empty() push() Insert an element top() Returns the top of heap element pop() Pop top element How to define as a small root heap: priority_queue<int, vector<int>, greater<int>> q; stack, Stack size() empty() push() Insert an element into the top of the stack top() Return stack top element pop() Pop up stack top element deque, Double ended queue size() empty() clear() front()/back() push_back()/pop_back() push_front()/pop_front() begin()/end() [] set, map, multiset, multimap, Based on balanced binary tree (red black tree), the ordered sequence is maintained dynamically size() empty() clear() begin()/end() ++, -- Return precursor and successor, time complexity O(logn) set/multiset insert() Insert a number find() Find a number count() Returns the number of a number erase() (1) The input is a number x,Delete all x O(k + logn) (2) Enter an iterator and delete it lower_bound()/upper_bound() lower_bound(x) Return greater than or equal to x Iterator of the smallest number of upper_bound(x) Return greater than x Iterator of the smallest number of map/multimap insert() The number of inserts is one pair erase() The input parameter is pair Or iterator find() [] be careful multimap This operation is not supported. The time complexity is O(logn) lower_bound()/upper_bound() unordered_set, unordered_map, unordered_multiset, unordered_multimap, Hashtable Similar to the above, the time complexity of adding, deleting, modifying and querying is O(1) I won't support it lower_bound()/upper_bound(), Iterator++,-- bitset, Pressure level bitset<10000> s; ~, &, |, ^ >>, << ==, != [] count() Returns how many 1s there are any() Determine whether there is at least one 1 none() Judge whether all are 0 set() Set all positions to 1 set(k, v) Will be the first k Bit becomes v reset() Change all bits to 0 flip() Equivalent to~ flip(k) Put the first k Bit inversion
lower_bound
Search for the first number greater than or equal to ≥ x in the array and return the iterator
No return end
#include<algorithm> vector<int> a; int pos = lower_bound(a.begin(), a.end(), x) - a.begin(); // pos is the target subscript int pos = lower_bound(a.begin(), a.end(), x, greater<int>() ) - a.begin() // Find the first number less than or equal to x
upper_bound
And lower_ The usage of bound is similar. Search the first number in the array greater than > x and return the iterator
No return end
#include<algorithm> vector<int> a; // pos is the target subscript int pos = upper_bound(a.begin(), a.end(), x) - a.begin(); // Find the first number less than x int pos = upper_bound(a.begin(), a.end(), x, greater<int>() ) - a.begin();
Basic algorithm
Divide and conquer
Divide and conquer method divides a problem into several similar sub problems with smaller scale, recursively solves these sub problems, and then deduces the solution of the original problem through them.
Divide and conquer to find the sum of equal ratio sequence
The fast power is used to find the sum of \ (1+p+\cdots+p^k \)
// Divide and conquer to find the equal ratio sequence and complexity (log k) ll sum(int p, int k){ if(!k) return 1; if(k & 1) return (1 + qmi(p, (k + 1) / 2)) % mod * sum(p, (k - 1) / 2) % mod; return ((1 + qmi(p, k / 2)) * sum(p, k / 2 - 1) + qmi(p, k)) % mod; }
Basic sorting algorithm
Merge sort
Recursion is divided into equal sub segments. After sorting within the sub segment, it is merged and reordered during backtracking
const int N = 1e6 + 10; int q[N],tmp[N]; void mergesort(int a[],int l,int r){ if(l >= r) return; int mid = (l + r) / 2; mergesort(q,l,mid); mergesort(q,mid+1,r); int k = 0,i = l,j = mid + 1; while(i <= mid && j <= r){ if(q[i] <= q[j]) tmp[k++] = q[i++]; else tmp[k++] = q[j++]; // Find the reverse order pair, followed by an ans += mid - i + 1; } while(i <= mid) tmp[k++] = q[i++]; while(j <= r) tmp[k++] = q[j++]; for(i = l,j = 0;i <= r;i++,j++) q[i] = tmp[j]; } int main(){ int n; scanf("%d",&n); for(int i = 0;i < n;i++){ scanf("%d",&q[i]); } mergesort(q,0,n-1); for(int i = 0;i < n;i++){ printf("%d ",q[i]); } return 0; }
Dichotomy
Integer dichotomy
bool check(int x) {/* ... */} // Check whether x meets certain properties // The interval [l, r] is divided into [l, mid] and [mid + 1, r]: int bsearch_1(int l, int r) { while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; // check() determines whether the mid satisfies the property else l = mid + 1; } return l; } // The interval [l, r] is divided into [l, mid - 1] and [mid, r]: int bsearch_2(int l, int r) { while (l < r) { int mid = l + r + 1 >> 1; if (check(mid)) l = mid; else r = mid - 1; } return l; }
Floating point binary
bool check(double x) {/* ... */} // Check whether x meets certain properties double bsearch_3(double l, double r) { const double eps = 1e-6; // eps indicates accuracy, which depends on the accuracy requirements of the subject while (r - l > eps) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } return l; }
Prefix sum, difference
Core idea
Change the query to \ and (o)
The difference changes the interval to \ (O(1) \), \ (O(n) \) to get the original array
One dimensional prefix sum
for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
2D prefix and
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) s[i] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
One dimensional difference
b[i] = a[i] - a[i - 1] // For [l, r] interval + c: b[l] += c, b[r + 1] -= c; // For [l, r] interval - c: b[l] -= c, b[r + 1] += c;
Two dimensional difference
// b[x1][y1] += c is to increase the value of the submatrix from the vertex to the lower right corner by c void insert(int x1, int y1, int x2, int y2, int c){ b[x1][y2 + 1] -= c; b[x2 + 1][y1] -= c; b[x2 + 1][y2 + 1] += c; b[x1][y1] += c; }
Double pointer
Core idea
Eliminate repetitive actions and optimize to achieve \ (O(n) \) complexity
Interval discretization
unique() + erase() function
vector<int> v; sort(v.begin(), v.end()); // unique only processes adjacent elements and requires sorting v.erase(unique(v.begin(), b.end()), v.end()); // unique returns the beginning of the last repeating element
Data structure and string algorithm
STL Foundation
queue
sliding window
Sliding window is essentially a monotonic two terminal queue
#include<deque> using namespace std; const int N = 1e6 + 10; deque<int> q; int a[N]; int n, k; // Sliding windows use double ended queues int main(){ cin >> n >> k; // k is the sliding window size for(int i = 1; i <= n; i++){ cin >> a[i]; } for(int i = 1; i <= n; i++){ // Gets the minimum value of the sliding window int x = a[i]; while(!q.empty() && i - q.front() > k - 1) q.pop_front(); while(!q.empty() && a[q.back()] >= a[i]){ // Pop when the tail of the team is greater than or equal to x_ back q.pop_back(); } q.push_back(i); if(i >= k) printf("%d ", a[q.front()]); } printf("\n"); q.clear(); for(int i = 1; i <= n; i++){ // Gets the maximum value of the sliding window int x = a[i]; while(!q.empty() && i - q.front() > k - 1) q.pop_front(); while(!q.empty() && a[q.back()] <= a[i]){ // If the tail of the team is less than x, it will pop_back q.pop_back(); } q.push_back(i); if(i >= k) printf("%d ", a[q.front()]); } return 0; }
Array simulation queue
int q[N]; int main(){ int T; cin >> T; int hh = 0, tt = -1; while(T--){ string op; cin >> op; if(op == "push"){ // Add elements to the end of the team int x; cin >> x; q[++tt] = x; } else if(op == "pop") // Pop up the head of the team hh++; else if(op == "empty"){ // Whether the query is empty if(hh <= tt) puts("NO"); else puts("YES"); } else // Query team leader printf("%d\n", q[hh]); } return 0; }
Stack
Array simulation stack
int s[N]; int main(){ int T; cin >> T; int tt = 0; // tt indicates the top of the stack while(T--){ string op; cin >> op; if(op == "push"){ // Push int x; cin >> x; s[++tt] = x; } if(op == "pop") // Pop stack top tt--; if(op == "empty"){ // Query whether the stack is empty if(tt > 0) puts("NO"); else puts("YES"); } if(op == "query") // Query stack top printf("%d\n", s[tt]); } return 0; }
Monotone stack
int s[N]; int main(){ int n; cin >> n; int tt = 0; for(int i = 0; i < n; i++){ int x; cin >> x; while(tt > 0 && s[tt] >= x) // The stack is not empty, and the top of the stack is greater than or equal to x tt--; if(!tt) printf("-1 "); else printf("%d ", s[tt]); s[++tt] = x; // Stack x } return 0; }
KMP
using namespace std; const int M = 1e6 + 10,N = 1e5 + 10; char p[N],s[M]; int n,m; int ne[M]; int main(){ cin >> m >> p+1 >> n >> s+1; // Preprocessing next array ne[1] = 0; // The first element has no suffix for(int i = 2,j = 0;i <= m;i++){ while(j && p[i] != p[j+1]) j = ne[j]; if(p[i] == p[j+1]) j++; ne[i] = j; } // Matching process for(int i = 1,j = 0;i <= n;i++){ while(j && s[i] != p[j+1]) j = ne[j]; if(s[i] == p[j+1]) j++; if(j == m){ cout << i - m << " "; j = ne[j]; // Optimization speed } } return 0; }
Trie tree
The size of the open \ (son \) array depends on the subject, which is generally \ (number of elements \ times stores the maximum number of nodes required for each element \)
int son[N][26], idx; // idx stores the node number. Node 0 is both a root node and an empty node int cnt[N]; void insert(char str[]){ // insert int p = 0; for(int i = 0; str[i]; i++){ int u = str[i] - 'a'; if(!son[p][u]) son[p][u] = ++ idx; // If the node does not exist, create a new one p = son[p][u]; } cnt[p] ++; // Mark the end node } int query(char str[]){ // query int p = 0; for(int i = 0; str[i]; i++){ int u = str[i] - 'a'; if(!son[p][u]) return 0; p = son[p][u]; } return cnt[p]; }
Persistent Trie
- There's not enough space. Go backwards with the maximum space and drive as large as you can
Example: maximum XOR sum
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 6e5 + 10, M = N * 24; // It used to be 3e5, 3e5 times. The length of the query sequence is up to 6e5, and each number is up to 24 bits. All the historical versions of Trie need up to 6e5 * 24 nodes int n, m; int tr[M][2], root[N], max_id[M], s[N], idx; // The idea of persistent trie: different from the previous version of trie, the points on the path should be re established // Topic idea: convert the XOR prefix sum, s[p - 1] ^ s[n] ^ x is the largest, and find a p in [l - 1, r - 1] to make s[p - 1] ^ (s[n] ^ x) the largest // Because you need to know the subscript of the child node to determine the subscript of the parent node, write it recursively void insert(int i, int k, int p, int q){ // In the subscript in the prefix and, the number of bits (from left to right) are inserted. The old version corresponds to the node and the new version corresponds to the node if(k <= -1){ max_id[q] = i; return ; } int v = (s[i] >> k) & 1; // Take out this bit tr[q][v] = ++ idx; // The path different from the original should be opened up if(p) tr[q][v ^ 1] = tr[p][v ^ 1]; // Copy directly with the same path as the original insert(i, k - 1, tr[p][v], tr[q][v]); // Recursive insertion max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]); // Maximum subscript of backtracking update subtree } int query(int root, int C, int l){ // Root node number, value to be XOR, subscript limit int p = root; for(int i = 23; i >= 0; i--){ int v = (C >> i) & 1; if(max_id[tr[p][v ^ 1]] >= l) // Find the opposite node and meet the requirements of 01 p = tr[p][v ^ 1]; else p = tr[p][v]; } return C ^ s[max_id[p]]; } int main(){ cin >> n >> m; max_id[0] = -1; // Null node subscript infinitesimal root[0] = ++ idx; // Must open up new nodes for the root insert(0, 23, 0, root[0]); // Insert, s[0] is also a prefix and int x; for(int i = 1; i <= n; i++){ cin >> x; s[i] = s[i - 1] ^ x; root[i] = ++ idx; // Open up new nodes for new roots insert(i, 23, root[i - 1], root[i]); } while(m--){ string op; cin >> op; if(op == "A"){ ++n; cin >> x; root[n] = ++ idx; // Open up new nodes for new roots s[n] = s[n - 1] ^ x; insert(n, 23, root[n - 1], root[n]); } else{ int l, r; cin >> l >> r >> x; cout << query(root[r - 1], s[n] ^ x, l - 1) << endl; // Interval [l, r], find p after conversion belongs to [l - 1, r - 1], find it in root[r - 1], subscript limit > = L - 1; } } return 0; }
Merge search set
Parallel query set initialization
for(int i = 1; i <= n; i++){ p[i] = i; Size[i] = 1; // Maintain connected block size with i as root }
Query + path compression
int find(int x){ if(x != p[x]) return p[x] = find(p[x]); return p[x]; }
Query + maintain distance from point to root node
int find(int x){ if(x != p[x]){ int root = find(p[x]); d[x] += d[p[x]]; p[x] = root; } return p[x]; }
Merge and query set
p[find(a)] = find(b); sz[b] += sz[a]; // Consolidated query set size of maintenance
Query whether two points are in the same set
if(find(a) == find(b))
Hashtable
storage structure
open addressing
\(N \) take \ (2 ~ 3 \) times of the input scale, and \ (null \) is the initialization value
const int N = 2e5 + 5, null = 0x3f3f3f3f; int h[N]; void insert(int x){ // insert int k = (x % N + N) % N; while(h[k] != null){ k++; if(k == N) k = 0; } h[k] = x; } int find(int x){ // lookup int k = (x % N + N) % N; while(h[k] != null){ if(h[k] == x) break; if(k == N) k = 0; k++; } return h[k]; }
Zipper method
Similar adjacency table
const int N = 1e5 + 3; // The first prime number larger than the size of the input data int e[N], ne[N], h[N], idx; void insert(int x){ // insert int k = (x % N + N) % N; e[idx] = x, ne[idx] = h[k], h[k] = idx++; } bool find(int x){ // lookup int k = (x % N + N) % N; for(int i = h[k]; i != -1; i = ne[i]){ if(e[i] == x) return true; } return false; }
String hash
Hash map the string prefix from left to right, high to low, and map it to \ (P \) hexadecimal number.
- Usually \ (P = 131 \) or \ (P = 13331 \)
- The array is written as unsigned long to achieve the purpose of automatic module extraction
- Get the hash value of \ ([l,r] \) string, \ (value=h_r-h_{l-1}\times p^{r-l+1} \)
typedef unsigned long long ull; // The maximum value of unsigned long is 2 ^ 64 - 1, which can realize automatic mold taking using namespace std; const int N = 1e5 + 10, P = 131; // P = 13331 ull p[N], h[N]; char str[N]; int n, m; void init(){ // Preprocessing p-ary p[0] = 1; for(int i = 1; i <= n; i++){ p[i] = p[i - 1] * P; h[i] = h[i - 1] * P + str[i]; // str[i] is not 0 } } ull get(int l, int r){ return h[r] - h[l - 1] * p[r - l + 1]; // Prefix and + base alignment }
ST table (RMQ algorithm)
Difference from segment tree: static query of interval maximum value, and modification is not supported
ST table initialization
void init(){ for(int i = 0; i < M; i++) // Similar to interval DP, enumerate the interval length first and then the starting point for(int j = 1; j + (1 << i) - 1 <= n; j++){ if(!i) st[j][0] = a[j]; else st[j][i] = max(st[j][i - 1], st[j + (1 << (i - 1))][i - 1]); } }
ST table query
int query(int l, int r){ int len = r - l + 1; int k = log(len) / log(2); // Less than the power of the maximum 2 of the interval length return max(st[l][k], st[r - (1 << k) + 1][k]); // The maximum value of the maximum value of the previous two segments is the maximum value of the interval }
Tree group
Essential function and expansion
- Interval query, query prefix and complexity \ (O(logn) \) (can be combined with difference)
- Single point modification: modify the complexity of array elements \ (O(logn) \).
- The number of occurrences of values can be used as the maintenance object to solve the problem of reverse order pairs
- On the basis of difference, it can be changed from single point query to interval and query. The method adopts the idea of complement set:
- Each element in the interval is the prefix sum of the differential array, and the interval sum is the sum of the elements of the differential array of two-layer loops
- Use the complement set to supplement a (x + 1, x) matrix. The originally required interval sum = matrix sum ((x + 1) * differential array prefix and [x]) - i * a[i] is the prefix and [x] of the element array;
- Handle the small problem of \ (k \) in the array (the array is an array):
- The tree group maintains an array with an element value of 1, representing that this number occurs once
- Delete this number, that is, add(x, -1), find the smallest K, and use the dichotomy method. Since the array has only 0 and 1, and the prefix sum is monotonous, find the smallest x, ask(x) = k, which is the smallest number \ (K \)
Three basic functions
int lowbit(int x){ return x & -x; } void add(int x, int c){ // Single point modification for(int i = x; i <= n; i += lowbit(i)) tr[i] += c; } int ask(int x){ // Query interval [1, x] int res = 0; for(int i = x; i; i -= lowbit(i)) res += tr[i]; return res; }
Segment tree
Five functions: push up, push down, build query, modify
Ordinary segment tree: supports interval query and single point modification
Segment tree marked with \ (lazy \): supports interval modification and requires pushdown operation
Key points and expansion
- The segment tree maintenance attributes are obtained according to the meaning of the topic. If the existing attributes are insufficient, add new attributes until they can be updated smoothly
- Maintenance interval maximum common divisor:
- Convert interval addition to single point modification (maintain differential array)
- Maximum common divisor of interval \ (GCD (a_1, a_2,..., a_n) < = > GCD (a_1, a_2 - a_1, a_3 - a_2,..., a_n - a_ {n-1}) \)
- Where \ (a_1 \) is calculated by the sum of the differential prefix (the interval sum needs to be maintained)
- The lazy labeling problem of interval addition and interval multiplication is maintained at the same time. Multiply first and then add
Segment tree classic template
Template I Maximum value of dynamic search interval (no pushdown)
#define ls u << 1 #define rs u << 1 | 1 using namespace std; const int N = 2e5 + 10; int n, m, p; struct T{ int l, r, v; }tr[N << 2]; void pushup(int u){ // Push up: the child node updates the parent node information tr[u].v = max(tr[ls].v, tr[rs].v); } void build(int u, int l, int r){ // Establish a segment tree, (node number, left end point of node interval, right end point of node interval) tr[u] = (T){l, r}; // Assign node interval first if(l == r) // return when you reach the leaf node return; int mid = (l + r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); // Continue recursion on two intervals (l, mid) (mid + 1, r) pushup(u); } int query(int u, int l, int r){ // Query operation, (node number, left end point of query interval, right end point of query interval) if(tr[u].l >= l && tr[u].r <= r) // The query interval completely includes the node interval and returns the node value return tr[u].v; int mid = (tr[u].l + tr[u].r) >> 1; int res = 0; if(l <= mid) res = query(ls, l, r); // The query interval has an intersection on the left of the node interval if(r > mid) res = max(res, query(rs, l, r)); // At the right intersection of the node interval, the result takes the maximum value (node value attribute definition) return res; } void modify(int u, int pos, int v){ // Single point modification, (node number, query point subscript, change value) if(tr[u].l == pos && tr[u].r == pos) // When the query point is reached, the node value is returned tr[u].v = v; else{ int mid = (tr[u].l + tr[u].r) >> 1; if(pos <= mid) modify(ls, pos, v); // The query point is on the left of the node interval, and query to the left subtree else modify(rs, pos, v); // Otherwise, query to the right subtree pushup(u); // When the child node changes, push up updates the information to the parent node } } int main(){ cin >> m >> p; build(1, 1, m); // Length up to m int last = 0; while(m --){ string op; long long d; cin >> op >> d; if(op == "Q"){ last = query(1, n - d + 1, n); cout << last << endl; } else{ d = (d + last) % p; modify(1, n++ + 1, d); } } return 0; }
Template II Interval modification problem (with pushdown)
#include<iostream> #include<cstring> #include<algorithm> #define ls u << 1 #define rs u << 1 | 1 typedef long long ll; using namespace std; const int N = 1e5 + 10; int n, m; ll a[N]; struct T{ int l, r; ll sum, add; // add is the lazy flag, and the subinterval needs to be modified }tr[N << 2]; void pushup(int u){ tr[u].sum = tr[ls].sum + tr[rs].sum; } void pushdown(int u){ // lazy tag drop tr[ls].sum += (tr[ls].r - tr[ls].l + 1) * tr[u].add, tr[ls].add += tr[u].add; // The parent node updates the child interval, and the child interval lazy mark should be added with the parent node lazy mark tr[rs].sum += (tr[rs].r - tr[rs].l + 1) * tr[u].add, tr[rs].add += tr[u].add; tr[u].add = 0; // The parent node's lazy flag is clear to ensure that the parent node's lazy flag is clear during query and modification } void build(int u, int l, int r){ if(l == r) tr[u] = (T){l, l, a[l], 0}; else{ tr[u].l = l, tr[u].r = r; int mid = (tr[u].l + tr[u].r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); pushup(u); } } void modify(int u, int l, int r, ll v){ if(tr[u].l >= l && tr[u].r <= r){ // Note the recursive exit of interval modification tr[u].sum += (tr[u].r - tr[u].l + 1) * v; tr[u].add += v; // lazy mark plus v } else{ pushdown(u); // pushdown before recursive splitting int mid = (tr[u].l + tr[u].r) >> 1; if(l <= mid) modify(ls, l, r, v); if(r > mid) modify(rs, l, r, v); pushup(u); } } ll query(int u, int l, int r){ if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum; else{ pushdown(u); // pushdown before recursive splitting int mid = (tr[u].l + tr[u].r) >> 1; ll res = 0; if(l <= mid) res = query(ls, l, r); if(r > mid) res += query(rs, l, r); return res; } } int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) cin >> a[i]; build(1, 1, n); while(m--){ string op; ll l, r, d; cin >> op >> l >> r; if(op == "Q") cout << query(1, l, r) << endl; else{ cin >> d; modify(1, l, r, d); } } return 0; }
Formwork III Atlantis (scanline algorithm)
#include<iostream> #include<cstring> #include<algorithm> #include<iomanip> #include<vector> #define ls u << 1 #define rs u << 1 | 1 #define pb push_back typedef long long ll; using namespace std; const int N = 1e4 + 10; int n; vector<double> ys; // Review the practice of scanning line more, and the expansibility is not strong struct Seg{ double x, y1, y2; int k; bool operator < (const Seg& s) const{ return x < s.x; } }seg[N << 1]; struct T{ int l, r, cnt; double len; }tr[N << 3]; // The space of segment tree is 4 times of the sequence maintained by segment tree int find(double y){ return lower_bound(ys.begin(), ys.end(), y) - ys.begin(); } void pushup(int u){ if(tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l]; // Nodes are covered, and the elements in ys represent intervals, which should express tr [u] R the desired element must be + 1 else if(tr[u].l != tr[u].r) // The node is not covered, but it is not a leaf node. len can be obtained from the child node tr[u].len = tr[ls].len + tr[rs].len; else tr[u].len = 0; // The node is not overwritten. It may be updated. cnt = 0. You need to set the len of the node to 0 } void build(int u, int l, int r){ if(l == r) tr[u] = (T){l, r, 0, 0}; else{ tr[u] = (T){l, r, 0 ,0}; int mid = (tr[u].l + tr[u].r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); } } void modify(int u, int l, int r, int k){ // The scan line does not need pushdown if(tr[u].l >= l && tr[u].r <= r){ tr[u].cnt += k; pushup(u); // This node has been updated. The parent node needs to be updated by pushing up } else{ int mid = (tr[u].l + tr[u].r) >> 1; if(l <= mid) modify(ls, l, r, k); if(r > mid) modify(rs, l, r, k); pushup(u); } } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int T = 0; while(cin >> n, n){ ys.clear(); for(int i = 0, j = 0; i < n; i++){ double x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2; seg[j++] = {x1, y1, y2, 1}; seg[j++] = {x2, y1, y2, -1}; ys.pb(y1), ys.pb(y2); } sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end()); sort(seg, seg + 2 * n); build(1, 0, ys.size() - 2); // The subscript is length - 1, and the record element indicates interval - 1 double res = 0<F8>; for(int i = 0; i < 2 * n; i++){ if(i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x); modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k); // A single element represents the node with the subscript value as the left end point, and the right end point of the interval maintained by the segment tree should be - 1 } cout << "Test case #" << ++T << endl; cout << "Total explored area: " << fixed << setprecision(2) << res << endl << endl; } return 0; }
Chairman tree (persistent segment tree)
Find the smallest \ (k \) of the interval
#include<iostream> #include<algorithm> #include<vector> using namespace std; const int N = 1e5 + 10; vector<int> v; int n, m, a[N], root[N], idx; // The chairman tree maintains the value domain space struct T{ int l, r, cnt; // l. r points to the left subtree and the right subtree. cnt records the number of values in the range maintained }tr[N * 4 + N * 17]; // O(N * 4 + N * logM); int find(int x){ return lower_bound(v.begin(), v.end(), x) - v.begin(); } int build(int l, int r){ // On the range interval [l, r] int p = idx++; if(l == r) // Is a leaf node return p; int mid = (l + r) >> 1; tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r); // Left subtree and right subtree build trees on [l, mid] and [mid + 1, r] return p; } int insert(int p, int l, int r, int x){ int q = idx++; // Open up new nodes tr[q] = tr[p]; // Replication node if(l == r){ tr[q].cnt++; // At the point of insertion, cnt++ return q; } int mid = (l + r) >> 1; if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x); // The point to be inserted is in the left half of the value range, and the left subtree of the new node should be different from the original else tr[q].r = insert(tr[p].r, mid + 1, r, x); // Otherwise, the right subtree is different tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt; // Update cnt from subtree, similar to pushup return q; } int query(int p, int q, int l, int r, int k){ // Contains a binary search (the interval characteristics maintained by the left and right subtrees of the line segment tree node have been determined) if(l == r) // Since the query interval must contain answers, if the end points of the interval are the same, it means that the target interval has been searched return l; int mid = (l + r) >> 1; int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt; // Because the tree structures of different versions are the same, the intervals maintained by nodes are actually the same, but cnt is different. cnt has the characteristic of monotonic increase. The subtraction before and after is actually the number of times between [L, R] if(cnt >= k) return query(tr[p].l, tr[q].l, l, mid, k); // If the number of left ends is greater than cnt, look to the left else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt); // Otherwise, search to the right and k -= cnt, because the number of CNTs is smaller than all the numbers on the right } int main(){ cin >> n >> m; for(int i = 1; i <= n; i++){ cin >> a[i]; v.push_back(a[i]); } sort(v.begin(), v.end()); // Discretization operation v.erase(unique(v.begin(), v.end()), v.end()); root[0] = build(0, v.size() - 1); // Root 0 builds a tree directly to form a structure, but does not update the cnt for(int i = 1; i <= n; i++) root[i] = insert(root[i - 1], 0, v.size() - 1, find(a[i])); // Insert new values in turn and update cnt while(m--){ int l, r, k; cin >> l >> r >> k; cout << v[query(root[l - 1], root[r], 0, v.size() - 1, k)] << endl; // Prefix and idea s[r] - s[l - 1] is what we want in the [l, r] interval. What we query is the subscript, and then convert the answer from the discretized array } return 0; }
Balanced tree
Treap
principle
- With BST (binary search tree) as the leading, the right subtree node key > parent node key > left subtree node key
- By adding val, BST has the property constraint of large root heap, which is relatively balanced and evolved into a tree equilibrium tree
Tree template
Template I
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 1e5 + 10, INF = 1e8; int idx, root; struct Node{ int l, r; int key, val; int cnt, size; }tr[N]; void pushup(int p){ // In the same way as the segment tree pushup, the child node updates the parent node tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt; } int get_node(int key){ // Create node and pass in key tr[++idx].key = key; tr[idx].val = rand(); // Random val tr[idx].cnt = tr[idx].size = 1; return idx; // Return node number } void zig(int& p){ // Dextral int q = tr[p].l; // Take the left son by right rotation tr[p].l = tr[q].r, tr[q].r = p, p = q; // The parent node runs to the right of the left son, the right of the left son becomes the parent node, and the pointer is adjusted to the left son pushup(tr[p].r), pushup(p); // pushup changed two points } void zag(int& p){ // Sinistral int q = tr[p].r; tr[p].r = tr[q].l, tr[q].l = p, p = q; // The parent node runs to the left of the right son, the left son of the right son becomes the parent node, and the pointer is adjusted to the right son pushup(tr[p].l), pushup(p); // pushup changed two points } void build(){ // Build a tree get_node(-INF), get_node(INF); // The establishment of sentinel nodes is conducive to dealing with the boundary root = 1, tr[1].r = 2; pushup(root); if(tr[1].val < tr[2].val) zag(root); } void insert(int &p, int key){ // Insert node if(!p) p = get_node(key); // This node does not exist. Create it directly else if(tr[p].key == key) tr[p].cnt++; // cnt if the node already exists++ else if(tr[p].key > key){ // To the left insert(tr[p].l, key); if(tr[tr[p].l].val > tr[p].val) zig(p); // The left son value is larger than the current node. After insertion, the left son needs to be adjusted to the parent node for right rotation } else{ // Insert to the right insert(tr[p].r, key); if(tr[tr[p].r].val > tr[p].val) zag(p); // The value of the right son is larger than the current node. After insertion, the right son needs to be adjusted to the parent node for left rotation } pushup(p); ///Update the following information to the node indicated by the p pointer. Since it is backtracking, it has the function of bottom-up } void rm(int& p, int key){ // Delete node if(!p) return; if(tr[p].key == key){ // Node exists if(tr[p].cnt > 1) tr[p].cnt--; // cnt > 1, cnt -- else if(tr[p].l || tr[p].r){ // Not a leaf node if(!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val){ // If the right subtree does not exist or the value on the left is greater than that on the right, the left son acts as the parent node for right rotation zig(p); rm(tr[p].r, key); // After right rotation, the original parent node is on the right side of the p pointer } else{ // Otherwise, the right son acts as the parent node and rotates left zag(p); rm(tr[p].l, key); // After left rotation, the original parent node is on the left of the p pointer } } else p = 0; // Node delete leaves directly } else if(tr[p].key > key) rm(tr[p].l, key); else rm(tr[p].r, key); pushup(p); // Update the following information to the node indicated by the p pointer. Since it is backtracking, it has the function of bottom-up } int get_rank_by_key(int p, int key){ // Find node ranking through key (the smallest) if(!p) return 0; if(tr[p].key == key) return tr[tr[p].l].size + 1; // Because it is the smallest ranking, it is the left subtree size + 1 else if(tr[p].key < key) // Find the ranking and look to the right return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key); // The returned result should be added with the left subtree and node size else // Look to the left return get_rank_by_key(tr[p].l, key); } int get_key_by_rank(int p, int rank){ // Find the key of the node through the node ranking if(!p) return 0; if(tr[tr[p].l].size >= rank) // The size of the left is larger than the rank. Go to the left return get_key_by_rank(tr[p].l, rank); else if(tr[tr[p].l].size + tr[p].cnt >= rank) // Plus you're bigger than rank, right at p return tr[p].key; else // Otherwise, on the right, the number smaller than that on the left should be subtracted from the ranking return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt); } int get_prev(int p, int key){ // Find the maximum number less than key if(!p) return -INF; if(tr[p].key >= key) return get_prev(tr[p].l, key); // Current key > = target key, look to the left else return max(tr[p].key, get_prev(tr[p].r, key)); // Current key < target key, the answer is in the current key or the subtree on the right } int get_next(int p, int key){ // Find the smallest decimal greater than key if(!p) return INF; if(tr[p].key <= key) return get_next(tr[p].r, key); // Current key < = target key, look to the right else return min(tr[p].key, get_next(tr[p].l, key)); // Current key > target key. The answer is in the current key or the subtree on the left } int main(){ int m; cin >> m; build(); // Establish balance tree while(m--){ int op, x; cin >> op >> x; if(op == 1) insert(root, x); else if(op == 2) rm(root, x); else if(op == 3) cout << get_rank_by_key(root, x) - 1 << endl; else if(op == 4) cout << get_key_by_rank(root, x + 1) << endl; else if(op == 5) cout << get_prev(root, x) << endl; else if(op == 6) cout << get_next(root, x) << endl; } return 0; }
Splay
Balance principle: each operation rotates the node to the root, and play (x, k) rotates \ (x \) below \ (k \)
Core: Splay always keeps the number of middle order traversals unchanged, and the number of \ (k \) actually maintains the number of \ (k \) of middle order traversals
Example: maintenance sequence (NOI 2005)
- Insert sequence
- Delete interval
- Change the continuous interval to the same number
- Flip interval
- Sum of intervals
- Maximum subsequence of summation interval
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 5e5 + 10, INF = 1e9 + 10; struct Node{ int s[2], v, p; // p parent node v value int sum, ms, ls, rs, size; // All values here mean the values after the lazy tag operation int same, rev; void init(int v_, int p_){ v = v_, p = p_; s[0] = s[1] = rev = same = 0, size = 1; // If there is reuse, the subtree should also be initialized sum = ms = v; ls = rs = max(v, 0); // ls and rs can be 0, but according to the meaning of the question, ms cannot be 0 } }tr[N]; int nodes[N], root, idx, n, m, w[N], tt; // nodes [] is similar to a stack, but it stores the node numbers that can be used // The child node updates the parent node, and there is a push up after modification void pushup(int u){ auto& t = tr[u], &l = tr[tr[u].s[0]], &r = tr[tr[u].s[1]]; t.size = l.size + r.size + 1; t.sum = l.sum + r.sum + t.v; // Different from the segment tree, the value t.v of the node is added here t.ms = max(max(l.ms, r.ms), l.rs + r.ls + t.v); t.ls = max(l.ls, l.sum + r.ls + t.v); t.rs = max(r.rs, r.sum + l.rs + t.v); } // The parent node updates the child node's lazy tag and adds it before recursion void pushdown(int u){ auto& t = tr[u], &l = tr[tr[u].s[0]], &r = tr[tr[u].s[1]]; if(t.same){ t.rev = t.same = 0; if(t.s[0]) l.v = t.v, l.same = 1, l.sum = t.v * l.size; // Only when the left and right subtrees exist can they be updated if(t.s[1]) r.v = t.v, r.same = 1, r.sum = t.v * r.size; if(t.v > 0){ if(t.s[0]) l.ls = l.rs = l.ms = l.sum; if(t.s[1]) r.ls = r.rs = r.ms = r.sum; } else{ if(t.s[0]) l.ls = l.rs = 0, l.ms = t.v; if(t.s[1]) r.ls = r.rs = 0, r.ms = t.v; } } else if(t.rev){ t.rev = 0, l.rev ^= 1, r.rev ^= 1; swap(l.ls, l.rs), swap(r.ls, r.rs); swap(l.s[0], l.s[1]), swap(r.s[0], r.s[1]); } } // Rotate (merge left and right) void rotate(int x){ int y = tr[x].p, z = tr[y].p; int k = tr[y].s[1] == x; tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z; tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y; tr[x].s[k ^ 1] = y, tr[y].p = x; pushup(y), pushup(x); } // Rotate node x below node K (k is 0, which means x is set as the root) void splay(int x, int k){ while(tr[x].p != k){ int y = tr[x].p, z = tr[y].p; if(z != k){ // If z is k, x rotates only once if(tr[z].s[0] == y != tr[y].s[0] == x) rotate(x); else rotate(y); } rotate(x); } if(!k) root = x; } // Find the k-th number in the middle order traversal sequence int get_k(int k){ int u = root; while(1){ pushdown(u); int ls_sum = tr[tr[u].s[0]].size; // Number of nodes in the left subtree (excluding the node itself) if(ls_sum >= k) u = tr[u].s[0]; // ls_ Sum > = k, indicating that the point to be found is in the left subtree (medium order ergodic property) else if(ls_sum + 1 == k) return u; // ls_sum+1==k, indicating that the point to be found is u else k -= ls_sum + 1, u = tr[u].s[1]; // Otherwise k-=ls_sum+1, go to the right subtree to find } return -1; } // Insert the node and use recursive tree build ing in this problem void insert(int v){ int u = root, p = 0; // u represents the node and p represents the parent node while(u) p = u, u = tr[u].s[v > tr[u].v]; // Search the node to be inserted according to v from top to bottom u = ++idx; if(p) tr[p].s[v > tr[p].v] = u; // If the parent node is not empty, bind the parent node to the node according to v tr[u].init(v, p); // initialization splay(u, 0); // After the operation, rotate the u to the root node } // Establishing a class complete binary tree on w[l, r] int build(int l, int r, int p){ int mid = (l + r) >> 1; int u = nodes[tt--]; tr[u].init(w[mid], p); if(l < mid) tr[u].s[0] = build(l, mid - 1, u); if(r > mid) tr[u].s[1] = build(mid + 1, r, u); pushup(u); // Push up after construction return u; } // Traversing the output subtree in middle order is useless in this problem void output(int u){ pushdown(u); if(tr[u].s[0]) output(tr[u].s[0]); if(tr[u].v >= 1 && tr[u].v <= n) cout << tr[u].v << " "; // Judge whether the node is a sentry if(tr[u].s[1]) output(tr[u].s[1]); } // Traverse the subtree and put the nodes into the recycling array void dfs(int u){ if(tr[u].s[0]) dfs(tr[u].s[0]); if(tr[u].s[1]) dfs(tr[u].s[1]); nodes[++tt] = u; } int main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; tr[0].ms = w[0] = w[n + 1] = -INF; // 0 and n + 1 are two sentinels, because sentinels are required for interval operation for(int i = 1; i < N; i++) nodes[++tt] = i; for(int i = 1; i <= n; i++) cin >> w[i]; root = build(0, n + 1, 0); // Assignment root node while(m--){ string op; int posi, tot, c; cin >> op; if(op == "INSERT"){ cin >> posi >> tot; for(int i = 0; i < tot; i++) cin >> w[i]; int l = get_k(posi + 1), r = get_k(posi + 2); // I was looking for [posi,posi+1], because there were sentinels, it was [posi+1,posi+2]; splay(l, 0), splay(r, l); // During operation, play (L, 0), play (R, l); The left subtree of R is the interval of [l,r] int u = build(0, tot - 1, r); // Convert sequence to balanced binary tree tr[r].s[0] = u; // Connected to the section pushup(r), pushup(l); } else if(op == "DELETE"){ cin >> posi >> tot; int l = get_k(posi), r = get_k(posi + tot + 1); splay(l, 0), splay(r, l); dfs(tr[r].s[0]); // Traverse the [l,r] subtree and put it into the recycling array tr[r].s[0] = 0; // Delete subtree pushup(r), pushup(l); } else if(op == "MAKE-SAME"){ cin >> posi >> tot >> c; int l = get_k(posi), r = get_k(posi + tot + 1); splay(l, 0), splay(r, l); auto& son = tr[tr[r].s[0]]; son.same = 1; // The root node of the subtree is the same=1, and then the value on the root node is updated son.v = c, son.sum = son.size * c; if(c > 0) son.ls = son.rs = son.ms = son.sum; else son.ls = son.rs = 0, son.ms = c; pushup(r), pushup(l); } else if(op == "REVERSE"){ cin >> posi >> tot; int l = get_k(posi), r = get_k(posi + tot + 1); splay(l, 0), splay(r, l); auto& son = tr[tr[r].s[0]]; swap(son.ls, son.rs); // Prefix and suffix, left and right subtrees should be flipped swap(son.s[0], son.s[1]); son.rev ^= 1; pushup(r), pushup(l); } else if(op == "GET-SUM"){ cin >> posi >> tot; int l = get_k(posi), r = get_k(posi + tot + 1); splay(l, 0), splay(r, l); cout << tr[tr[r].s[0]].sum << endl; } else cout << tr[root].ms << endl; } return 0; }
Tree cover tree
Segment tree set balance tree
Principle: the segment tree maintains the whole interval, and the balance tree maintains the ordered sequence in the node interval of the segment tree
- When only single point modification and interval predecessor and successor are involved, multiset can be set up in the segment tree node to replace the balance tree
Example: tree covering tree
- The integer of the query \ [R, x] is within the range \ [l].
- l,r, k, query the value ranked as \ (k \) in the interval \ ([l,r] \).
- pos x, change the number of positions of \ (pos \) to \ (x \).
- l r x, query the precursor of integer \ (x \) in the interval \ ([l,r] \) (precursor is defined as the maximum number less than \ (x \).
- l r x, query the successor of the integer \ (x \) in the interval \ ([l,r] \) (the successor is defined as the smallest number greater than \ (x \).
#define ls u << 1 #define rs u << 1 | 1 using namespace std; const int N = 5e4 + 10, M = 1500010, INF = 1e9; // Space calculation: the segment tree node is 4*n, each node is fixed with 2 sentinels, which is equal to 4*n * 2. There are a total of logn layers, and the length of each layer is n // The balance tree node n * logn, the last space 8 * n + logn * n < = > 1200000, and 1500001 if you turn it up struct Splay{ int v, p, s[2]; int size; void init(int v_, int p_){ v = v_, p = p_; size = 1; } }spl[M]; int idx, w[N], n, m; void pushup(int u){ spl[u].size = spl[spl[u].s[0]].size + spl[spl[u].s[1]].size + 1; } void rotate(int x){ int y = spl[x].p, z = spl[y].p; int k = spl[y].s[1] == x; spl[z].s[spl[z].s[1] == y] = x, spl[x].p = z; spl[y].s[k] = spl[x].s[k ^ 1], spl[spl[x].s[k ^ 1]].p = y; spl[x].s[k ^ 1] = y, spl[y].p = x; pushup(y), pushup(x); } void splay(int x, int k, int& root){ while(spl[x].p != k){ int y = spl[x].p, z = spl[y].p; if(z != k){ if(spl[y].s[0] == x != spl[z].s[0] == y) rotate(x); else rotate(y); } rotate(x); } if(!k) root = x; } int get_less(int v, int root){ // Find out how many numbers are smaller than the value v int u = root, res = 0; while(u){ if(v > spl[u].v) res += spl[spl[u].s[0]].size + 1, u = spl[u].s[1]; else u = spl[u].s[0]; } return res; } int get_pre(int v, int& root){ // Get the prefix of v int u = root, res = -INF; while(u){ if(v > spl[u].v) res = max(res, spl[u].v), u = spl[u].s[1]; else u = spl[u].s[0]; } return res; } int get_next(int v, int root){ // Get the suffix of v int u = root, res = INF; while(u){ if(v < spl[u].v) res = min(res, spl[u].v), u = spl[u].s[0]; else u = spl[u].s[1]; } return res; } void insert(int v, int& root){ int u = root, p = 0; while(u) p = u, u = spl[u].s[v > spl[u].v]; u = ++idx; if(p) spl[p].s[v > spl[p].v] = u; spl[u].init(v, p); splay(u, 0, root); } struct T{ int l, r, u; }tr[N << 2]; void update(int x, int y, int& root){ // Find the point with the value of x in the play and update it to y int u = root; while(u){ if(spl[u].v == x) break; else if(spl[u].v < x) u = spl[u].s[1]; else u = spl[u].s[0]; } splay(u, 0, root); // Rotate node x to root int l = spl[u].s[0], r = spl[u].s[1]; while(spl[l].s[1]) l = spl[l].s[1]; while(spl[r].s[0]) r = spl[r].s[0]; splay(l, 0, root), splay(r, l, root); // Find the previous node and the next node of x in the sequence, and play to the root spl[r].s[0] = 0; // Delete node x pushup(r), pushup(l); // Push up (R) and push up (L) after deletion (modification) insert(y, root); // Insert y and insert will nest play and rotate with push up } void change(int u, int pos, int x){ update(w[pos], x, tr[u].u); // update is required as long as the interval maintained by the segment tree node contains pos if(tr[u].l == tr[u].r) return; int mid = (tr[u].l + tr[u].r) >> 1; if(pos <= mid) change(ls, pos, x); else change(rs, pos, x); } int query(int u, int l, int r, int x){ // Query x's ranking if(tr[u].l >= l && tr[u].r <= r){ return get_less(x, tr[u].u) - 1; // Find the number less than x, because - 1 is required to include sentinels } int mid = (tr[u].l + tr[u].r) >> 1, res = 0; if(l <= mid) res += query(ls, l, r, x); // The number of the whole interval less than x = the sum of the numbers of the left and right intervals less than x if(r > mid) res += query(rs, l, r, x); return res; } int query_pre(int u, int l, int r, int x){ if(tr[u].l >= l && tr[u].r <= r) return get_pre(x, tr[u].u); int mid = (tr[u].l + tr[u].r) >> 1, res = -INF; if(l <= mid) res = max(res, query_pre(ls, l, r, x)); // The maximum number less than x in the interval is the max of the number less than x in the left and right intervals if(r > mid) res = max(res, query_pre(rs, l, r, x)); return res; } int query_next(int u, int l, int r, int x){ if(tr[u].l >= l && tr[u].r <= r) return get_next(x, tr[u].u); int mid = (tr[u].l + tr[u].r) >> 1, res = INF; if(l <= mid) res = min(res, query_next(ls, l, r, x)); // Same as above if(r > mid) res = min(res, query_next(rs, l, r, x)); return res; } void build(int u, int l, int r){ tr[u].l = l, tr[u].r = r; // tr[u].u will change with the function reference insert(-INF, tr[u].u), insert(INF, tr[u].u); // Every balance tree should be joined by sentinels for(int i = l; i <= r; i++) insert(w[i], tr[u].u); if(tr[u].l == tr[u].r) return; int mid = (tr[u].l + tr[u].r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); } int main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; for(int i = 1; i <= n; i++) cin >> w[i]; build(1, 1, n); for(int i = 1; i <= m; i++){ int op, l, r, x, pos, k; cin >> op; if(op == 1){ // x ranks in [l,r] cin >> l >> r >> x; cout << query(1, l, r, x) + 1 << endl; // The number of query queries is less than x, and it needs + 1 to become the ranking of X } else if(op == 2){ // Value of rank k in [l,r] cin >> l >> r >> k; int a = 0, b = 1e8; while(a < b){ // Due to the monotonicity of ranking and value, the number ranked k can be found by dichotomy int mid = (a + b + 1) >> 1; if(query(1, l, r, mid) + 1 <= k) a = mid; // The number of query queries is less than x, and it needs + 1 to become the ranking of X else b = mid - 1; } cout << b << endl; } else if(op == 3){ cin >> pos >> x; change(1, pos, x); w[pos] = x; } else if(op == 4){ cin >> l >> r >> x; cout << query_pre(1, l, r, x) << endl; } else{ cin >> l >> r >> x; cout << query_next(1, l, r, x) << endl; } } return 0; }
Segment tree nested segment tree
Example: query the largest number of \ (K \)
- Offline processing stores all the data to be inserted
- The outer weight segment tree maintains the weight, and the inner ordinary segment tree maintains the subscript interval within the weight range, which supports interval addition and summation
- Interval labeling is used to perpetuate the optimization constant
#define ls u << 1 #define rs u << 1 | 1 #define pb push_back using namespace std; // Offline processing + weight segment tree set dynamic opening point segment tree maintenance interval information + mark permanence // It can also be solved by tree array + line segment tree + overall bisection const int N = 5e4 + 10, M = 1e7 + 3e6 + 10; struct Q{ int op, l, r, x; }q[N]; struct T1{ int l, r, u; }tr1[N << 2]; int n, m, idx; vector<int> alls; struct T2{ int l, r, add, sum; // l. R is the left and right son }tr2[M]; int inter(int l, int r, int ql, int qr){ return min(qr, r) - max(l, ql) + 1; } void update(int u, int l, int r, int ql, int qr){ // l. R is the left and right end points of tr2 interval tr2[u].sum += inter(l, r, ql, qr); if(l >= ql && r <= qr){ tr2[u].add ++; return ; } int mid = (l + r) >> 1; if(ql <= mid){ if(!tr2[u].l) tr2[u].l = ++ idx; update(tr2[u].l, l, mid, ql, qr); } if(qr > mid){ if(!tr2[u].r) tr2[u].r = ++ idx; update(tr2[u].r, mid + 1, r, ql, qr); } } int get_sum(int u, int l, int r, int ql, int qr, int add){ int res = 0; if(l >= ql && r <= qr) return tr2[u].sum + (r - l + 1) * add; add += tr2[u].add; // The marks of the node itself remain unchanged, and the passed marks should be accumulated int mid = (l + r) >> 1; if(ql <= mid){ if(!tr2[u].l) res += inter(l, mid, ql, qr) * add; else res += get_sum(tr2[u].l, l, mid, ql, qr, add); } if(qr > mid){ if(!tr2[u].r) res += inter(mid + 1, r, ql, qr) * add; else res += get_sum(tr2[u].r, mid + 1, r, ql, qr, add); } return res; } int get(int x){ return lower_bound(alls.begin(), alls.end(), x) - alls.begin(); } int query(int u, int l, int r, int x){ if(tr1[u].l == tr1[u].r) return tr1[u].l; int mid = (tr1[u].l + tr1[u].r) >> 1; int k = get_sum(tr1[rs].u, 1, n, l, r, 0); // Get the number of subtrees on the right if(k >= x) return query(rs, l, r, x); else return query(ls, l, r, x - k); } void change(int u, int l, int r, int x){ update(tr1[u].u, 1, n, l, r); if(tr1[u].l == tr1[u].r) return ; int mid = (tr1[u].l + tr1[u].r) >> 1; if(x <= mid) change(ls, l, r, x); else change(rs, l, r, x); } void build(int u, int l, int r){ tr1[u].l = l, tr1[u].r = r, tr1[u].u = ++ idx; if(tr1[u].l == tr1[u].r) return; int mid = (tr1[u].l + tr1[u].r) >> 1; build(ls, l, mid), build(rs, mid + 1, r); } int main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; for(int i = 1; i <= m; i++){ cin >> q[i].op >> q[i].l >> q[i].r >> q[i].x; if(q[i].op == 1) alls.pb(q[i].x); } sort(alls.begin(), alls.end()); alls.erase(unique(alls.begin(), alls.end()), alls.end()); build(1, 0, alls.size() - 1); for(int i = 1; i <= m; i++){ if(q[i].op == 1) change(1, q[i].l, q[i].r, get(q[i].x)); else cout << alls[query(1, q[i].l, q[i].r, q[i].x)] << endl; } return 0; }
AC automata
principle
- Many are similar to KMP algorithm, but have their own optimization (Trie graph optimization)
Application and expansion
- Using the ne [] array feature, cnt [] records the occurrence times of all word prefixes at the same time, accumulates all CNTs from the back to the front, records the position of word nodes, and can count the occurrence times of all words in the string
Trie graph optimization template
#include<iostream> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int N = 1e4 + 10, S = 55; // Number of nodes = number of words * maximum length of words int son[N * S][26], idx, ne[N * S]; int cnt[N * S], n; string str; void insert(string s){ // Trie insert int len = s.size(), p = 0; for(int i = 0; i < len; i++){ int t = s[i] - 'a'; if(!son[p][t]) son[p][t] = ++idx; p = son[p][t]; } cnt[p]++; } void build(){ // Similar to kmp, wide search is carried out in two dimensions, layer by layer int len = str.size(); queue<int> q; for(int i = 0; i < 26; i++) // Put all the sub nodes in the first layer first, and i in kmp starts to cycle from 2 if(son[0][i]) q.push(son[0][i]); while(q.size()){ auto t = q.front(); q.pop(); for(int i = 0; i < 26; i++){ // Loop through all possible child nodes int& p = son[t][i]; if(!p) p = son[ne[t]][i]; // If the child node does not exist, jump to the node pointed to by the parent node ne pointer else{ ne[p] = son[n[t]][i]; // if(p[i] == p[j + 1]) ne[i] = ++j; q.push(p); } } } } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); int T; cin >> T; while(T--){ memset(son, 0, sizeof son); memset(cnt, 0, sizeof cnt); memset(ne, 0, sizeof ne); cin >> n; for(int i = 0; i < n; i++){ cin >> str; insert(str); } cin >> str; build(); int len = str.size(); long long res = 0; for(int i = 0, j = 0; i < len; i++){ int t = str[i] - 'a'; j = son[j][t]; // Since the end node points to the empty node, that is, the root, it is equivalent to traversing the whole trie int p = j; while(p){ res += cnt[p]; cnt[p] = 0; // The visited node cnt should be cleared to avoid double calculation p = ne[p]; // Node pointed to by ne pointer } } cout << res << endl; } return 0; }
Search and graph theory
DFS deep search
enumeration
Exponential enumeration
Select and deselect
// Exponential enumeration void dfs(int u){ if(u > n){ for(auto t: v) printf("%d ", t); printf("\n"); return ; } v.pb(u); // choose dfs(u + 1); v.pob(); dfs(u + 1); // Not selected }
\(n \) queen enumeration example
The main difference between the two is that the information maintained by dfs parameters leads to different search order
Enumerate according to the selection and non selection of each grid
void dfs(int x, int y, int s){ if(y == n){ // Beyond the horizontal axis, switch to the next layer to continue enumeration y = 0; x++; } if(x == n){ if(s == n){ for(int i = 0; i < n; i++) puts(g[i]); puts(""); } return; // return to prevent stack explosion } // Deselect the current grid dfs(x, y + 1, s); // Select current grid if(!dg[x + y] && !udg[x - y + n] && !row[x] && !col[y]){ g[x][y] = 'Q'; dg[x + y] = udg[x - y + n] = row[x] = col[y] = true; dfs(x, y + 1, s + 1); dg[x + y] = udg[x - y + n] = row[x] = col[y] = false; g[x][y] = '.'; } }
Enumerate by column
// Enumerate by row void dfs(int u){ if(u == n){ for(int i = 0; i < n; i++) puts(g[i]); puts(""); } for(int i = 0; i < n; i++){ // Enumeration column if(!col[i] && !udg[u - i + n] && !dg[u + i]){ g[u][i] = 'Q'; col[i] = udg[u - i + n] = dg[u + i] = true; dfs(u+1); col[i] = udg[u - i + n] = dg[u + i] = false; g[u][i] = '.'; } } }
Combinatorial enumeration
// Combinatorial enumeration void dfs(int u){ if(ans.size() > m || ans.size() + (n - u + 1) < m) // Exponential enumeration pruning return; if(ans.size() == m){ for(auto t: ans) printf("%d ", t); printf("\n"); return; } ans.pb(u); dfs(u + 1); ans.pob(); dfs(u + 1); }
Permutation enumeration
// Permutation enumeration void dfs(int u){ // u records where the current enumeration is located if(u > n){ // After enumerating everything, output it and return for(auto t : ans) printf("%d ", t); printf("\n"); return; } for(int i = 1; i <= n; i++){ if(!st[i]){ st[i] = true; ans.pb(i); dfs(u + 1); ans.pob(); // to flash back st[i] = false; } } }
BFS wide search
bfs searches slowly in circles.
The following two points should be considered in solving the problem:
- What information does the node in the queue maintain
- How to solve the representation of distance in bfs
global template
BFS must pay attention to the information maintained by a single node in the queue, which is very important.
int bfs(){ queue<PII> q; q.push({1,1}); d[1][1] = 0; // Mark the distance from the upper left corner and initialize it to - 1. If it is not - 1, it means that it has been accessed while(q.size()){ auto t = q.front(); q.pop(); for(int i = 0; i < 4; i++){ int x= t.first + dx[i], y = t.second + dy[i]; if(x < 1 || x > n || y < 1 || y > m || d[x][y] != -1 || g[x][y] == 1) continue; d[x][y] = d[t.first][t.second] + 1; q.push({x,y}); } } return d[n][m]; }
Eight digital examples
- The queue node is \ (string \)
- The hash table maps the distance of each string state
int dx[4] = {0,0,1,-1}, dy[4] = {1,-1,0,0}; unordered_map<string, int> d; string ed = "12345678x"; int bfs(string st){ queue<string> q; q.push(st); d[st] = 0; while(q.size()){ auto t = q.front(); q.pop(); int dist = d[t]; if(t == ed) return dist; int k = t.find('x'); for(int i = 0; i < 4; i++){ int x = k / 3 + dx[i], y = k % 3 + dy[i]; if(x < 0 || x > 2 || y < 0 || y > 2) continue; swap(t[k], t[x * 3 + y]); if(!d.count(t)){ d[t] = dist + 1; q.push(t); } swap(t[k], t[x * 3 + y]); } } return -1; }
Storage of trees and graphs
- Tree is a special graph, which is stored in the same way as graph
- For undirected graphs \ (ab \), store two directed edges, \ (a - > b \), \ (B - > a \)
Adjacency matrix storage
g[a][b] = a->b
Adjacency table storage
int h[N], e[N], ne[N], w[N], idx; // Add edge a - > b void add(int a, int b, int c){ e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } int main(){ memset(h, -1, sizeof h); ... return 0; }
Traversal of trees and graphs
Time complexity
\(O(n + m) \), \ (n \) represents the number of points, \ (m \) represents the number of sides
dfs depth first traversal
Examples
AcWing 846. The center of gravity of the tree
dfs returns int, which can maintain the number of nodes in the subtree
void dfs(int u){ st[u] = true; for(int i = h[u]; ~i; i = ne[i]){ int j = e[i]; if(!st[j]) dfs(j); } }
bfs width first traversal
Sometimes you can use an array to maintain both tag and distance functions
#include<queue> void bfs(){ queue<int> q; q.push(1); // First element listing st[1] = true; // Mark when listed while(q.size()){ // Queue is not empty int t = q.front(); // Take the lead q.pop(); // Remember pop for(int i = h[t]; ~i; i = ne[i]){ // Traverse all directions int j = e[i]; if(!st[j]){ // Not reached or qualified for traversal q.push(j) st[j] = true; // Join the team and mark } } } }
Topological sorting
Topological graph is also called directed acyclic graph. The left and right order in the topological sequence must meet the starting point to the end point of the edge.
void topo(){ queue<int> q; for(int i = 1; i <= n; i++){ if(!indegree[i]){ q.push(i); ans.pb(i); } } while(q.size()){ auto t = q.front(); q.pop(); for(int i = h[t]; i != -1; i = ne[i]){ int j = e[i]; indegree[j]--; if(!indegree[j]){ q.push(j); ans.pb(j); } } } }
Shortest path problem
\(n \) represents the number of points and \ (m \) represents the number of sides.
- Single source shortest circuit
- All edge weights are positive
- Naive dijkstra algorithm \ (O(n^2) \) (suitable for dense graphs)
- Heap optimization dijkstra algorithm \ (O(m*logn) \) (suitable for sparse graphs, \ (m \) and \ (n \) of one order of magnitude)
- Existence of negative weight edge
- Bellman Ford algorithm \ (O(n*m) \) (no more than \ (k \) edges can be used)
- SPFA algorithm generally \ (O(m)\), \(O(n*m) \)
- All edge weights are positive
- Multi source sink shortest path
- Floyd algorithm \ (O(n^3) \)
Dijkstra algorithm
Plain Dijkstra \(O(n^2) \)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 510; // point int g[N][N],dist[N]; // g [] [] accesses points and edges in the form of adjacency matrix, and dist [] stores the shortest distance from each point to the first point bool st[N]; // Record whether the shortest circuit has been determined at each point int n,m; int dijkstra(){ memset(dist,0x3f,sizeof dist); dist[1] = 0; // Cycle n times for(int i = 1;i <= n;i++){ int t = -1; // Find out the point with the shortest distance from the first point in the undetermined shortest path point (greedy proof, just memorize it) for(int j = 1;j <= n;j++) if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j; st[t] = true; // sign for(int j = 1;j <= n;j++) dist[j] = min(dist[j],g[t][j] + dist[t]); } if(dist[n] == 0x3f3f3f3f) return -1; else return dist[n]; } int main(){ scanf("%d%d",&n,&m); memset(g,0x3f,sizeof g); while(m--){ int x,y,z; scanf("%d%d%d",&x,&y,&z); // The self loop is automatically ignored, and the double edge is the shortest g[x][y] = min(g[x][y],z); } cout << dijkstra() << endl; return 0; }
Heap optimization Dijkstra \(O(m*logm = m *logn) \)
#include<iostream> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int N = 150010; // Represents a point, first represents the shortest distance from the point to the root node, and second represents the number of the point typedef pair<int,int> PII; int e[N],ne[N],h[N],w[N],idx; // Sparse graph is stored in the form of adjacency table int n,m,dist[N]; // dist [] record the shortest distance from each point to the root node bool st[N]; // Has the marked point been updated with the minimum value void add(int a,int b,int c){ w[idx] = c; e[idx] = b; ne[idx] = h[a];h[a] = idx++; } int dijkstra(){ // Priority queue, incremental storage, establishment of small root heap and storage of the shortest edge. This heap cannot delete or modify specified elements priority_queue <PII, vector<PII>, greater<PII>> heap; memset(dist,0x3f,sizeof dist); // Initialize dist [] to infinity // The distance from the first node to itself is 0 dist[1] = 0; heap.push({0,1}); while(heap.size()){ PII t = heap.top(); // Take out the point with the shortest distance heap.pop(); int distance = t.first, ver = t.second; // Extract the number of the shortest distance if(st[ver]) continue; // If the point has updated the shortest distance to the root node, continue st[ver] = true; // Not updated, marked as true // Use this point to update the distance of other points for(int i = h[ver];i != -1;i = ne[i]){ int j = e[i]; // If (distance from J to origin) > (distance from origin to t + distance from t to j), update the shortest distance of J if(dist[j] > distance + w[i]){ dist[j] = distance + w[i]; heap.push({dist[j],j}); // Add the updated information of point j to the heap } } } if(dist[n] == 0x3f3f3f3f) return -1; // It indicates that the distance of n is not updated, and there is no connected edge from the origin to n return dist[n]; } int main(){ scanf("%d%d",&n,&m); memset(h,-1,sizeof h); // The initialization header of adjacency table is - 1 while(m--){ int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); // The following functions automatically use the shortest edge, and the double edge problem can be ignored } cout << dijkstra() << endl; return 0; }
Bellman Ford algorithm
const int N = 510,M = 10010, INF = 0x3f3f3f3f; int dist[N],last[N]; // dist [] records the distance to the origin, and last [] makes a backup, because the existence of negative weight edge prevents series connection int n,m,k; // Save points and edges of structure array struct Edge{ int a,b,w; }edges[M]; int bellman_ford(){ memset(dist, 0x3f,sizeof dist); dist[1] = 0; for(int i = 0;i < k;i++){ // The shortest distance of k edges memcpy(last,dist,sizeof dist); // Copy for(int j = 0;j < m;j++){ // Update shortest distance auto e = edges[j]; // Similar to dijkstra, note that the latter distance is the backup distance last[e.a] dist[e.b] = min(dist[e.b], last[e.a] + e.w); } } return dist[n]; // If there is a negative weight edge, judge dist [n] > inf / 2 and output impossible. The specific value depends on the topic range }
spfa algorithm
spfa finding the shortest path
\The codes of (spfa \) and \ (dijkstra \) are similar, and the algorithm of \ (bellman_ford \) mainly lies in:
- Instead of traversing all the edges, update the other points with the point whose \ (dist \) value becomes smaller
int spfa(){ memset(dist,0x3f,sizeof dist); // Initialization distance dist[1] = 0; queue<int> q; // Establish a queue and put it into the first point. The queue accesses the point number q.push(1); st[1] = true; // Mark that the first point has been put into the queue // Queue is not empty while(q.size()){ int t = q.front(); // Take the head of the team q.pop(); st[t] = false; // The tag header is no longer in the queue for(int i = h[t];i != -1;i = ne[i]){ int j = e[i]; if(dist[j] > dist[t] + w[i]){ dist[j] = dist[t] + w[i]; // Update distance // If j is not in the queue, it is put in the queue to update other points if(!st[j]){ q.push(j); st[j] = true; } } } } if(dist[n] == 0x3f3f3f3f) return -1; else return dist[n]; }
spfa judging negative ring
- Do not initialize the \ (dist \) array
- Each point should be added to the queue in advance
int ne[N],e[N],w[N],dist[N],h[N],idx,n,m; bool st[N]; int cnt[N]; // Record the number of sides to go through at a certain point // The code body is the same as spfa. There are two main differences: the initialization of distanceless dist, and each point is put into the queue in advance void add(int a,int b,int c){ e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++; } bool spfa(){ queue<int> q; for(int i = 1;i <= n;i++){ q.push(i); st[i] = true; } while(q.size()){ int t = q.front(); q.pop(); st[t] = false; for(int i = h[t];i != -1;i = ne[i]){ int j = e[i]; if(dist[j] > dist[t] + w[i]){ dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; // + 1 based on the cnt of the previous point // cnt greater than n indicates that there are at least n+1 points. According to the drawer principle, there must be a ring and a negative ring if(cnt[j] >= n) return true; if(!st[j]){ q.push(j); st[j] = true; } } } } return false; // Return false if there is no exception }
Floyd algorithm
- Based on \ (DP \), \ (f[k][i][j] \) represents the shortest path from \ (I \) through \ (K \) to \ (j \)
- Handling self loop is to put \ (g[i][i] = 0 \)
State transition equation:
void init(){ memset(g, 0x3f, sizeof g); for(int i = 1; i <= n; i++) g[i][i] = 0; // Self loop is 0 } void floyd(){ for(int k = 1; k <= n; k++) for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) g[i][j] = min(g[i][j], g[i][k] + g[k][j]); // Optimized the first dimensional space }
minimum spanning tree
\(n \) represents the number of points and \ (m \) represents the number of sides
- \(Prim \) algorithm
- Plain version \ (Prim\) \(O(n^2) \) (dense map)
- Heap optimization \ (Prim\) \(O(m*logn) \) (not commonly used)
- \(Kruskal \) algorithm \ (O(m*logm) \) (sparse graph)
Plain Prim algorithm \ (O(n^2) \)
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 510,INF = 0x3f3f3f3f; int g[N][N],dist[N]; // Adjacency matrix stores the midpoint and edge of the graph, dist [] accesses the distance from a point to the set (the shortest distance to all points in the set) int n,m; bool st[N]; // Tag array int prim(){ memset(dist,INF,sizeof dist); int res = 0; for(int i = 0;i < n;i++){ int t = -1; // t is in the for loop for(int j = 1;j <= n;j++) if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j; if(i && dist[t] == INF) return INF; // Indicates that the graph is not connected if(i) res += dist[t]; // Write res first and then update dist to exclude self ring // Note that dist[j] and g[t][j] take the minimum and distinguish them from dijkstra for(int j = 1;j <= n;j++) dist[j] = min(dist[j],g[t][j]); // sign st[t] = true; } return res; } int main(){ cin >> n >> m; memset(g,INF,sizeof g); // Initialize edge to infinity while(m--){ int u,v,w; scanf("%d%d%d",&u,&v,&w); g[u][v] = g[v][u] = min(g[u][v],w); // Undirected graph } int t = prim(); if(t == INF) puts("impossible"); else printf("%d",t); return 0; }
Heap optimized Prim algorithm \ (O(m * logn) \)
#include <cstring> #include <iostream> #include <queue> using namespace std; const int MAXN = 510, MAXM = 2 * 1e5 + 10, INF = 0x3f3f3f3f; typedef pair<int, int> PII; int h[MAXM], e[MAXM], w[MAXM], ne[MAXM], idx; bool vis[MAXN]; int n, m; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ; } int Prim() { memset(vis, false, sizeof vis); int sum = 0, cnt = 0; priority_queue<PII, vector<PII>, greater<PII>> q; q.push({0, 1}); while (!q.empty()) { auto t = q.top(); q.pop(); int ver = t.second, dst = t.first; if (vis[ver]) continue; vis[ver] = true, sum += dst, ++cnt; for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (!vis[j]) { q.push({w[i], j}); } } } if (cnt != n) return INF; return sum; } int main() { cin >> n >> m; memset(h, -1, sizeof h); for (int i = 0; i < m; ++i) { int a, b, w; cin >> a >> b >> w; add(a, b, w); add(b, a, w); } int t = Prim(); if (t == INF) cout << "impossible" << endl; else cout << t << endl; }
Kruskal (kruskam)
- Sort all edges from small to large
- Enumerate all the edges. If the endpoints are not in the same connected block, they will be connected until all the points are connected
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 2e5 + 10, INF = 0x3f3f3f3f; int n, m, p[N]; void init(){ // Initialize and query set for(int i = 1; i <= n; i++) p[i] = i; } int find(int x){ // Find operation if(x != p[x]) return p[x] = find(p[x]); return p[x]; } struct Edge{ // Structure edge saving int a, b, w; bool operator < (const Edge& W) const{ return w < W.w; } }edges[N]; int kruskal(){ int cnt = 0, res = 0;; for(int i = 0; i < m; i++){ if(cnt >= n - 1) break; int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find(a), b = find(b); if(a != b){ // A and b are not in a connected block p[b] = a; cnt++; res += w; } } if(cnt < n - 1) // If the number of connected edges is less than n - 1, there is no minimum spanning tree return INF; return res; } int main(){ cin >> n >> m; init(); for(int i = 0; i < m; i++){ int a, b, c; cin >> a >> b >> c; edges[i] = {a, b, c}; } sort(edges, edges + m); // Sort all edges from small to large int ans = kruskal(); if(ans == INF) puts("impossible"); else printf("%d", ans); return 0; }
Sub small spanning tree
The next smallest spanning number is the adjacent set of the minimum spanning tree, and only one edge is different from the minimum spanning tree.
There are two ways to calculate the secondary small spanning tree:
- Find the minimum spanning tree, enumerate and delete each edge of the minimum spanning tree, and then find the minimum spanning tree in turn (this small spanning tree cannot be guaranteed) \ (O(mlogm + nm) \)
- Find the minimum spanning tree, mark whether each edge is in the minimum spanning tree, preprocess the maximum value and sub maximum value of edge weight in the path from the midpoint to the point of the tree, enumerate non tree edges, add the edge to the tree, and delete the tree edge with the maximum edge weight or sub maximum edge weight (when the enumerated edge length is equal to the maximum edge weight), Finally, we get the small spanning tree (we can get the strict small spanning tree) \ (O(mlogm + n^2) \)
#include<iostream> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 510, M = 1e4 + 10; int h[N], e[2 * N], ne[2 * N], w[2 * N], idx; int dist1[N][N], dist2[N][N], n, m, p[N]; // dist1 [] [] saves the maximum edge weight from point to point, and dist [] [] saves the second largest edge weight // Finding sub small spanning tree struct Edge{ int a, b, w; bool f; bool operator < (const Edge & W) const{ return w < W.w; } }edges[M]; void init(){ for(int i = 1; i <= n; i++) p[i] = i; } int find(int x){ if(x != p[x]) p[x] = find(p[x]); return p[x]; } void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[]){ d1[u] = maxd1; // According to the characteristics of the minimum spanning tree, you can update directly d2[u] = maxd2; for(int i = h[u]; ~i; i = ne[i]){ int j = e[i]; if(j != fa){ int t1 = maxd1, t2 = maxd2; // It must be stored with temporary variables, and maxd1 and maxd2 will be used later; if(w[i] > t1){ // Update the largest and second largest edges t2 = t1; t1 = w[i]; } else if(w[i] > t2 && w[i] < t1) // Update minor edge t2 = w[i]; dfs(j, u, t1, t2, d1, d2); } } } int main(){ cin >> n >> m; init(); memset(h, -1, sizeof h); for(int i = 0; i < m; i++) cin >> edges[i].a >> edges[i].b >> edges[i].w; sort(edges, edges + m); ll sum = 0, res = 1e18; for(int i = 0; i < m; i++){ auto t = edges[i]; int a = t.a, b = t.b, w = t.w; int pa = find(a), pb = find(b); if(pa != pb){ p[pa] = pb; sum += w; edges[i].f = true; // Mark as tree edge add(a, b, w), add(b, a, w); // Build a tree } } for(int i = 1; i <= n; i++) // Maximum edge weight and sub maximum edge weight between preprocessing points dfs(i, -1, 0, 0, dist1[i], dist2[i]); for(int i = 0; i < m; i++){ if(!edges[i].f){ auto t = edges[i]; int a = t.a, b = t.b, w = t.w; if(w > dist1[a][b]) // Greater than maximum edge res = min(res, sum + w - dist1[a][b]); else if(w > dist2[a][b]) // Less than or equal to the maximum edge weight, greater than the second largest edge weight res = min(res, sum + w - dist2[a][b]); } } cout << res << endl; return 0; }
Bipartite graph
-
Staining method \ (O(n + m) \)
-
Hungarian algorithm \ (O(m*n) \), actually less than \ (O(m*n) \)
A bipartite graph has no odd rings and is divided into two parts without edges, that is, the two ends of an edge in the original graph cannot be of the same color, otherwise it is not a bipartite graph
Judge whether bipartite graph by coloring method
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 2e5 + 10; // Template idea: a bipartite graph has no odd rings and is divided into two parts without edges, that is, the two ends of an edge in the original graph cannot be of the same color, otherwise it is not a bipartite graph int n,m; int e[N],ne[N],h[N],idx; // Adjacency table storage map int color[N]; // Color array, divided into two colors, 1 and 2 void add(int a, int b){ e[idx] = b,ne[idx] = h[a],h[a] = idx++; } bool dfs(int u,int c){ color[u] = c; for(int i = h[u];i != -1;i = ne[i]){ int j = e[i]; if(!color[j]){ if(!dfs(j,3 - c)) return false; // Dye into 3-c color, original 1 is dyed 2, original 2 is dyed 1 } else if(color[j] == c) return false; // Same color as another point, non bipartite graph } return true; } int main(){ scanf("%d%d",&n,&m); memset(h,-1,sizeof h); while(m--){ int a,b; scanf("%d%d",&a,&b); add(a,b),add(b,a); } bool flag = true; for(int i = 1;i <= n;i++){ if(!color[i]) if(!dfs(i,1)){ // Dyeing problem flag = false; break; } } if(flag) puts("Yes"); else puts("No"); }
Maximum matching number of bipartite graphs
#include<iostream> #include<cstring> #include<algorithm> using namespace std; const int N = 510,M = 1e5 + 10; int ne[M],e[M],h[N],idx; // Adjacency table storage map int n1,n2,m; int match[N]; // Which boy does each girl correspond to bool st[N]; // Mark whether the girl has matched for a boy void add(int a,int b){ e[idx] = b,ne[idx] = h[a],h[a] = idx++; } bool find(int x){ for(int i = h[x];i != -1;i = ne[i]){ int j = e[i]; if(!st[j]){ st[j] = true; // The girl has not matched, or the boy who has matched can find another suitable one if(match[j] == 0 || find(match[j])){ match[j] = x; return true; } } } return false; // If it doesn't work, return false } int main(){ scanf("%d%d%d",&n1,&n2,&m); memset(h,-1,sizeof h); // Initialize header while(m--){ int a,b; scanf("%d%d",&a,&b); add(a,b); } int res = 0; for(int i = 1;i <= n1;i++){ memset(st,false,sizeof st); // For each boy to match from front to back, memset is required if(find(i)) res++; } printf("%d\n",res); return 0; }
number theory
Fast power & high precision
Counting
typedef long long ll; // solution1 ll qmi(ll a, ll k, ll p){ // a^k mod p ll res = 1; while(k){ if(k & 1) res = res * a % p; // int res = 1LL * res * a % p k >>= 1; a = a * a % p; // int a = 1LL * a * a % p; } return res; } // solution 2 int qmi(int a, long long b){ int res = 1; for(; b; b >>= 1){ if(b & 1) res = (long long) res * a % mod; a = (long long) a * a % mod; } return res; } // solution 3 long long quick_pow(long long x,long long y, ll p) { if(y==1) return x; if(y==0) return 1; //The codes in case 1 and 2 must be placed before case 3 if(y%2==0) return quick_pow(x*x%p,y/2); if(y%2!=0) return x*quick_pow(x*x%p,y/2)%p; }
Fast acceleration (high precision multiplication)
With modulus, \ (a*b\mod p \)
// solution1 is similar to the fast power idea, which splits k into binary numbers typedef long long ll; ll qmult(ll a, ll k, ll p){ ll res = 0; while(k){ if(k & 1) res = (res + a) % p; k >>= 1; a = (a * 2) % p; } return res; } // solution2 is derived from the advanced guide, using the automatic modulo of ull and the reserved integer of long double ull mul(ull a, ull b, ull p){ a %= p, b %= p; ull c = (long double) a * b / p; ull x = a * b, y = c * p; ull res = x - y; if(res < 0) res += p; return res; }
No modulus, \ (a*b \)
vector<int> mul(vector<int> a, int b) { vector<int> c; int t = 0; for (int i = 0; i < a.size(); i ++ ) { t += a[i] * b; c.push_back(t % 10); t /= 10; } while (t) { c.push_back(t % 10); t /= 10; } // while(C.size()>1 && C.back()==0) C.pop_ back();// Consider that only when b==0 can there be pop redundant 0 B= 0 doesn't need this line return c; }
Prime number
Determination of prime number by trial division
bool is_prime(int x) { if (x < 2) return false; for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) return false; return true; }
Decomposition of prime factor by trial division
No matter how decomposed, the cycle range must be within \ (sqrt(n) \), and the prime numbers greater than \ (sqrt(n) \) are judged separately.
void divide(int x) { for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) // If i is a prime divisor, i must be a prime divisor. So it's not composite { int s = 0; while (x % i == 0) x /= i, s ++ ; cout << i << ' ' << s << endl; // i-bit factor s is exponential } if (x > 1) cout << x << ' ' << 1 << endl; // Note that the maximum factor will remain cout << endl; } // another version int p[N], c[N], cnt; // p [] stores all the prime factors, c [] stores the index of the corresponding prime factor, and cnt stores the number of prime factors void divide(int n) { cnt = 0; for(int i = 2; i <= n / i; i++){ if(n % i == 0){ p[++cnt] = i, c[cnt] = 1; while(n % i == 0) n /= i, c[cnt]++; } } if(n > 1) p[++cnt] = n, c[cnt] = 1; }
Plain screen \ (O(nloglogn) \)
int primes[N], cnt; // Primes [] stores all primes bool st[N]; // st[x] is storage x screened out void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (st[i]) continue; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true; } }
Linear sieve (Euler sieve) \ (O(n) \)
int primes[N], cnt; // Primes [] stores all primes bool st[N]; // st[x] is storage x screened out void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) { st[i * primes[j]] = true; if (i % primes[j] == 0) break; } } }
Divisor
gcd maximum common divisor
typedef long long ll; ll gcd(ll a, ll b){ return b ? gcd(b, a % b) : a; }
Number of divisors and sum of divisors
-
\(prime factor decomposition of a\subset Z: \ BF {a = \ PI {I = 1} ^ KP I ^ {n I}}, K is the number of prime factors and n I is a prime factor index \)
-
\(find the number of divisors of \ forall a\subset N ^ +: \)
\(\ quad \because there are n+1 choices for each p_i index of 0,1\dots n, and different divisors can be obtained by multiplying different p_i. \)
\(\ Quad \ thereforeis obtained from the multiplication principle, and the number of divisors is \ prod {I = 1} ^ k (n _i + 1) \)
-
\(find the sum of all divisors of \ forall a\subset N ^ +: \)
\ (\ because \forall m is the divisor of a, and M is obtained by multiplying each prime factor by its different exponent \)
\ (\ therefore sum of divisors = (p_1^0+p_1^1+\dots+p_1^{n_0})\times\dots\times()(p_k^0+p_k^1+\dots+p_k^{n_k}) \)
\ (simplify the above formula to: \)
\ (sum of divisors = \ PI {J = 1} ^ k (\ sum {I = 0} ^ NP _j ^ I) \)
unordered_map<int, int> primes; const int p = 1e9 + 7; typedef long long ll; // Theorem of the number of divisors: prime factor decomposition a = π (pi^(ai^n)), in each pi part, there are 0~ai^n, a total of n + 1 methods, and the conclusion can be obtained from the multiplication principle // Sum of divisors theorem: a = π (∑ PI ^ a (ij)) (outside I, inside j) int main(){ int n; scanf("%d", &n); while(n --){ int x; scanf("%d", &x); for(int i = 2; i <= x / i; i++){ while(x % i == 0){ // while primes[i] ++; x /= i; } } if(x > 1) primes[x]++; } // Approximate number ll ans = 1; for(auto t: primes){ ans = ans * (t.second + 1) % p; } printf("%lld\n", ans); // Sum of divisors ll ans = 1; for(auto t: primes){ ll k = 1; while(t.second--){ k = (k * t.first + 1) % p; } ans = (ans * k) % p; } printf("%lld\n", ans); return 0; }
Euler function
Realization of Euler function formula
\(\phi(n)=n\times\Pi_{i=1}^k(1-\frac{1}{p_i})\color{black},p_ I is the prime factor of N and K is the number of prime factors \)
\(\ phi(n) \) size is less than the number of Coprime integers of \ (n \) and \ (n \).
typedef long long ll; // Euler function: for n = n * π (1 - 1/pi), pi is each prime factor ll ans = a; for(int i = 2; i <= a / i; i++){ if(a % i == 0){ ans = ans / i * (i - 1); // Divide i first to prevent decimal and overflow while(a % i == 0) a /= i; } } if(a > 1) ans = ans / a * (a - 1); printf("%lld\n", ans);
Finding Euler function by sieve method \ (O(nloglogn) \)
Find the prefix sum of the Euler function of \ (n \)
typedef long long ll; // Euler function: f(n) = n * π (1 - 1 ÷ pi) int primes[N]; bool st[N]; int phi[N]; int cnt = 0; ll get_eulers(int n){ phi[1] = 1; // The Euler function of 1 is 1 for(int i = 2; i <= n; i++){ if(!st[i]){ primes[cnt++] = i; phi[i] = i - 1; } for(int j = 0; primes[j] <= n / i; j++){ st[primes[j] * i] = true; if(i % primes[j] == 0){ phi[i * primes[j]] = phi[i] * primes[j]; // primes[j] has appeared in phi[i]. Just multiply primes[j] break; } // primes[j] is the smallest prime factor of i * primes[j], and it is not the prime factor of i phi[i * primes[j]] = phi[i] * (primes[j] - 1); // = phi[i] * (phi[primes[j]) } } ll ans = 0; // longlong explosion proof int for(int i = 1; i <= n; i++) ans += phi[i]; return ans; }
Extended Euclidean algorithm (exgcd)
exgcd
\(for a bunch of integers, find \ color{navy}{\exists x,y\subset Z}\color{black}, so that \ color{navy}{a\times x+b\times y=gcd(a,b)} \)
// The equation is established by D = GCD (a, b) = GCD (B, a% B) = ax + by = ax + B (Y - B * (A / b)) void exgcd(int a, int b, int &x, int &y){ if(!b){ x = 1, y = 0; return; } // printf("before:a=%d b=%d x=%d y=%d\n", a, b, x, y); exgcd(b, a % b, y, x); // Recurse all the way to the boundary, and the specific values of x and y will not change // printf("after:a=%d b=%d x=%d y=%d\n", a, b, x, y); y -= a / b * x; // Backtracking updates the values of x and y }
Solving linear congruence equation
\(\ color{black} calculate an x_i for each group, so that \ color{purple}{a_i\times x_i\equiv b_i (mod\space m_i)}, \ color{black} has no solution output \ color{red}{impossible} \)
\(the equation is transformed into ax + my = b. if there is a solution, there must be gcd(a, m) | b \)
int main(){ int n; scanf("%d", &n); while(n--){ int a, b, m; scanf("%d%d%d", &a, &b, &m); int d = gcd(a, m); if(b % d){ // If there is a solution, there must be gcd(a, m) | b puts("impossible"); continue; } int x = 0, y = 0; exgcd(a, m, x, y); printf("%d\n", (ll)x * b / d % m); // Just multiply x by b / d, open ll, and% m } return 0; }
Chinese remainder theorem
Gauss elimination
Combination number
Basic model
Diaphragm method
AcWing basic course talked about \ (4 \) methods for finding combination numbers, which deal with different data ranges respectively
Number of combinations \ (I \)
meaning of the title
Find \ (C_a^b \mod (10^9 + 7) \)
Data range
\(1≤n≤10000, 1≤b≤a≤2000\)
thinking
\(C_a^b = C_{a-1}^{b-1} + C_{a-1}^{b}\)
const int N = 2010, mod = 1e9 + 7; int c[N][N]; // c[a][b] -> C_a^b void init(){ for(int i = 0; i < N; i++) for(int j = 0; j <= i; j++) // b <= a if(!j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod; // % mod } int main(){ int T; cin >> T; init(); while(T--){ int a, b; cin >> a >> b; cout << c[a][b] << endl; } return 0; }
Number of combinations \ (II \)
meaning of the title
Find \ (C_a^b \mod (10^9 + 7) \)
Data range
\(1≤n≤10000, 1≤b≤a≤10^5\)
thinking
- The factorial of numerator and denominator is preprocessed
- \(a/b \equiv a *b^{-1} \mod p\), \(b^{-1} \) is the multiplicative inverse of \ (B \) module \ (P \)
const int N = 100010, mod = 1e9 + 7; typedef long long ll; int fact[N], infact[N]; // Preprocessing factorial (n * logn) int qmi(int a, int k){ int res = 1; while(k){ if(k & 1) res = 1LL * res * a % mod; a = 1LL * a * a % mod; k >>= 1; } return res; } int main(){ fact[0] = 1, infact[0] = 1; for(int i = 1; i < N; i++){ fact[i] = 1LL * fact[i - 1] * i % mod; // a / b congruence a * B ^ {- 1}% mod - > A * B ^ {mod - 2}; Fermat's theorem infact[i] = 1LL * infact[i - 1] * qmi(i, mod - 2) % mod; } int T; cin >> T; while(T--){ int a, b; cin >> a >> b; // Middle% mod anti overflow long long cout << 1LL * fact[a] * infact[b] % mod * infact[a - b] % mod << endl; } return 0; }
Combinatorial number \ (III \) (\ (Lucas \) theorem)
meaning of the title
Enter \ (a,b,p \) and find the value of \ (C_a^b\mod p \)
Data range
\(1≤n≤20\)
\(1≤b≤a≤10^{18}\)
\(1≤p≤10^5\)
thinking
- Using the \ (Lucas \) theorem:
int qmi(int a, int k, int p){ int res = 1; while(k){ if(k & 1) res = 1ll * res * a % p; a = 1ll * a * a % p; k >>= 1; } return res; } int C(int a, int b, int p){ if(b > a) return 0; int res = 1; // C_a^b = \frac{a*(a-1)*\cdots *(a - b + 1)}{b!}; for(int i = 1, j = a; i <= b; i++, j--){ res = 1ll * res * j % p; // Fermat's small theorem, a / b congruence, a * b^{-1} module p, b^{-1} = b^{p-2} res = 1ll * res * qmi(i, p - 2, p) % p; } return res; } // Lucas theorem: int lucas(ll a, ll b, int p){ // Parameters a and B take long and long if(a < p && b < p) return C(a, b, p); return 1ll * C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; } int main(){ int T; cin >> T; while(T--){ ll a, b, p; cin >> a >> b >> p; cout << lucas(a, b, p) << endl; } return 0; // 21 }
Number of combinations \ (IV \)
meaning of the title
Enter \ (a,b \) and find the value of \ (C_a^b \)
The result is large and requires high-precision calculation (no \ (\% \) operation)
Data range
\(1\leq b\leq a\leq 5000\)
thinking
- Sieve prime number
- Find the number of times each prime number appears in factorial
- Traversing prime numbers, high-precision multiplication
int primes[N], cnt; int sum[N]; bool st[N]; int a, b; // Linear sieve void get_primes(int n){ for(int i = 2; i <= n; i++){ if(!st[i]) primes[cnt++] = i; for(int j = 0; primes[j] <= n / i; j++){ st[i * primes[j]] = true; if(i % primes[j] == 0) break; } } } // Get n! Index with p int get(int n, int p){ int res = 0; while(n){ res += n / p; n /= p; } return res; } // High precision multiplication vector<int> mult(vector<int> a, int b){ vector<int> c; int t = 0; for(int i = 0; i < a.size(); i++){ t += a[i] * b; c.push_back(t % 10); t /= 10; } while(t){ c.push_back(t % 10); t /= 10; } return c; } int main(){ cin >> a >> b; get_primes(a); for(int i = 0; i < cnt; i++){ int p = primes[i]; sum[i] = get(a, p) - get(b, p) - get(a - b, p); } vector<int> res; res.push_back(1); for(int i = 0; i < cnt; i++){ int p = primes[i]; for(int j = 0; j < sum[i]; j++){ res = mult(res, p); } } // Reverse order output for(int i = res.size() - 1; i >= 0; i--) cout << res[i]; return 0; }
Catalan number
Examples
AcWing889. 01 sequence satisfying conditions
AcWing129. The train entered the stack
Inclusion exclusion principle
Time complexity
Usually \ (O(2^n) \)
Examples
#include<iostream> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; const int N = 20; int p[N]; int main(){ int n, m; cin >> n >> m; for(int i = 0; i < m; i++) cin >> p[i]; ll res = 0; for(int i = 1; i < 1 << m; i++){ // Binary enumeration idea ll t = 1, cnt = 0; for(int j = 0; j < m; j++){ if(i >> j & 1){ t *= p[j]; // There may be overflow here. Open longlong if(t > n){ t = -1; break; } cnt ++; } } if(t != -1){ if(cnt & 1) // Decide whether to add or subtract according to the number of sets res += n / t; else res -= n / t; } } cout << res << endl; return 0; }
Game theory
nature
- Finiteness: no matter how they make a decision, they will decide the outcome after a limited step.
- Fairness: that is, two people follow the same rules in making decisions.
P/N status
P-position: P stands for Previous. The situation where the person who took the last action had a winning strategy is p-position, that is, "the first hand will lose"
N-position: N stands for Next. The situation where the current action person has a winning strategy is n-position, that is, "the first hand can guarantee the winning"
Point P: that is, the point where the player must lose when both sides are in the optimal strategy
N point: that is, the winning point. Under the optimal strategy of both sides, the player will win at this point
Winning state and losing state
- If the person facing the end state wins, the end state is winning, otherwise the end state is losing
- The necessary and sufficient condition for a situation to be a winning state is that the situation becomes a winning state after making some decision
- The necessary and sufficient condition for a situation to be a failure state is that the situation will become a win state no matter what decision is made
nim game
If the exclusive or sum of the number of stones in each pile is not 0, the first hand must win, otherwise lose.
SG function
SG theorem
#include<unordered_set> // SG theorem: sg(b1, b2) = sg(b1) ^ sg(b2) // SG function int sg(int x){ if(f[x] != -1) return f[x]; unordered_set<int> S; for(int i = 0; i < x; i++){ for(int j = 0; j <= i; j++) S.insert(sg(i) ^ sg(j)); } for(int i = 0; ; i++) // The XOR value of sg function may be greater than x. do not write conditions if(!S.count(i)) return f[x] = i; }
dynamic programming
\(dp \) core idea
A set represents multiple states to form optimization
Digital triangle model
Example: pass a note
#include<iostream> #include<cstring> using namespace std; const int N = 55; int w[N][N]; int f[N + N][N][N]; // It is the same as the grid status and will not be repeated // Status indication: f[k][i1][i2] indicates that the maximum number is obtained when walking to (i1, k-i1)(i2, k-i2) // When i1 + j1 == i2 + j2, the two paths may coincide, and the end point is f[2n][n][n]; // State transition: f[k][i1][i2] = max(f[k-1][i1-1][i2],..[i1][i2-1],...[i1][i2],...[i1-1][i2-1]) + w; // When the values of W coincide, take w[i1][k - i1], and if they do not coincide, add w[i2][k - i2]; The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source. int main(){ int n, m; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &w[i][j]); for(int k = 2; k <= n + m; k++){ for(int i1 = 1; i1 <= n; i1++){ for(int i2 = 1; i2 <= n; i2++){ int j1 = k - i1, j2 = k - i2; if(j1 < 1 || j1 > m || j2 < 1 || j2 > m) continue; int t = w[i1][j1]; if(i1 != i2) t += w[i2][j2]; int &x = f[k][i1][i2]; x = max(x, f[k - 1][i1][i2] + t); x = max(x, f[k - 1][i1 - 1][i2] + t); x = max(x, f[k - 1][i1][i2 - 1] + t); x = max(x, f[k - 1][i1 - 1][i2 - 1] + t); // cout << x << " " << endl; } } } printf("%d", f[n + m][n][n]); // Take n for both coordinates, because I1 and I2 are abscissa return 0; }
LIS model
Knapsack model
// 01 backpack: // Full backpack: 532 Monetary system // Multiple backpacks: // Group backpack: 487 Jin Ming's budget is 1020 Diver 1013 Machine allocation // Specific scheme: 1013 Machine allocation
Enter unified \ (n \) to represent the number of items and \ (m \) to represent the volume of the backpack
Scroll array optimization:
- Use the state of \ (f[i-1] \) to enumerate the volume from large to small
- Use the state of \ (f[i] \) to enumerate the volume from small to large
be careful ⚠️: You need to find a specific scheme, and you can't optimize the rolling array
01 Backpack
Core: choose or not
General meaning:
Given \ (n \) items, each item has a value of \ (w_i \) and only one, and the backpack volume is \ (v \), find the maximum value of all schemes
Status indication: \ (f[i][j] \) indicates the maximum value of all schemes whose volume does not exceed \ (j \) in the first \ (I \) articles
State calculation: \ (f[i][j]=max(f[i-1][j],f[i -1][j - v_i] + w_i) \)
// Rolling array (one-dimensional space optimization) for(int i = 1; i <= n; i++) for(int j = m; j >= v[i]; j--) f[j] = max(f[j], f[j - v[i]] + w[i]);
Complete Backpack
State transition certificate:
- A value \ (f[i][j -v] + w \) represents all the maximum values of the prefix. Recursive optimization
\(\because f[i][j] = max(f[i-1][j], f[i-1][j-v]+w,f[i-1][j-2v]+2w,\dots )\)
\(\& f[i][j-v] = max(f[i-1][j-v], f[i-1][j-v]+w, ...)\)
\(\therefore f[i][j] = max(f[i][j], f[i][j-v_i] + w_i)\)
General meaning:
There are \ (N \) items and a backpack with a capacity of \ (V \), with unlimited items available. The \ (I \) article has a volume of \ (v_i \) and a value of \ (w_i \).
Solve which items are selected to enter the backpack, which can maximize the total value of the backpack without exceeding the capacity.
Status indication: \ (f[i][j] \) indicates the maximum value of all schemes with a total volume of no more than \ (j \) selected from the first \ (I \) articles
State calculation: \ (f[i][j]=max(f[i-1][j],f[i][j - v_i] + w_i) \)
// Rolling array (one-dimensional space optimization) for(int i = 1; i <= n; i++){ // Enumerate each item group for(int j = v; j <= m; j++) // Unlike the 01 knapsack, the state is transferred from f[i][j-v[i]], so j is enumerated from small to large f[j] = max(f[j], f[j - v[i]] + w[i]); }
Multiple Backpack
Core: Master \ (2 \) multiple knapsack optimization methods (binary optimization & monotone queue optimization) for different data ranges
General meaning:
There are \ (N \) items and a backpack with a capacity of \ (V \). The \ (I \) item has a maximum of \ (s_i \), each with a volume of \ (v_i \) and a value of \ (w_i \).
Solve which items are loaded into the backpack, so that the total volume of items does not exceed the capacity of the backpack, and the total value is the largest.
Multiple backpacks \ (I \)
Data range:
\(0<N,V≤100\)
\ (0 < v_i, w_i, s_i ≤ 100 \) (not absolutely about this order)
Status indication: $f[i][j] $indicates that the total volume of the first \ (I \) items does not exceed the maximum value of \ (j \)
State calculation: \ (f[i][j] = max(f[i][j], f[i-1][j - k * v_i + k * w_i]) \)
Consider this kind of multiple backpack as a special complete backpack, or a complete backpack as a special multiple backpack. Heap cycle
(code omitted)
Multiple knapsack \ (II \) (binary optimization)
Time complexity: \ (O(n\times m\times \log{s}) \)
Data range:
\(0<N≤1000\)
\(0<V≤2000\)
\(0<v_i,w_i,s_i≤2000\)
Binary optimization into 01 knapsack proof:
\ (\ because each s_i can be expressed in binary as s_i=1+2+4+8+\dots+2^{k-1}+c\quad(c can not be a power of 2) \)
\ (suppose that for each s_i, C < 2 ^ k, it is divided into k items, then the volume of each item is 2^{k_i}\times w \)
\ (\ therefore all items are divided into cnt items (cnt = \ sum {J = 1} ^ {n}{K J}), and the selection decision of each item is a 01 knapsack problem \)
const int N = 25000; // 2000 * 1000 * log2000 int n,m; int f[N]; int v[N], w[N], s[N]; int main(){ scanf("%d%d", &n, &m); int cnt = 0; // Record the total number of items divided into for(int i = 1; i <= n; i++){ int a, b, s; scanf("%d%d%d", &a, &b, &s); // Volume mass number int k = 1; while(k <= s){ // s = 1 + 2 + 4 +...+ 2^k + c; cnt ++; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2; } if(s > 0){ // Residual constant c cnt ++; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt; for(int i = 1; i <= n; i++){ // The circulation part is the same as 01 backpack for(int j = m; j >= v[i]; j--){ f[j] = max(f[j], f[j - v[i]] + w[i]); } } printf("%d\n", f[m]); return 0; }
Multiple knapsack \ (III \) (monotonic queue optimization)
Time complexity: \ (O(n\times m) \)
Data range:
\(0<N≤1000\)
\(0<V≤20000\)
\(0<v_i,w_i,s_i≤20000\)
Monotone queue optimization proof:
For details, please refer to this title
At the beginning of each enumeration of item groups, the backup array saves the information of \ (f[i-1] \), and then uses the one-dimensional state
Express \ (m \) as \ (k * V + J \) \ ((J = m \% v) \), \ (f [J + k * V] = max (f [J + k_i * V] + (k-k_i) * W) \ Quad while (k_i ≤ k) \)
Because you want to maintain the maximum value with a monotonic queue, the number of queue heads cannot change, so it is converted to:
\(f[j+k*v]=max(f[j+k_i*v-k_i*w])+k*w\)
The monotone queue maintenance problem has two important points:
1. Maintain the number of queue elements. If you can't continue to join the queue, pop up the queue header element 2. Maintain the monotonicity of the queue, that is: $Tail value >= dp[j + k*v] - k*w$
// Queue elements maintain the subscript of f [] int main(){ int n, V; cin >> n >> V; while(n--){ int v, w, s; cin >> v >> w >> s; memcpy(g, f, sizeof f); // Backup the status of dp[i-1] [] with g [] for(int j = 0; j < v; j++){ int hh = 0, tt = -1; // Resets the queue every time j is enumerated for(int k = j; k <= V; k += v){ if(hh <= tt && q[hh] < k - v * s) // Too many window elements hh++; if(hh <= tt) // Queue exists, update f[k], g[q[hh]] itself contains + (q[hh] - j)/v*w; f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w); while(hh <= tt && g[q[tt]] - (q[tt] - j) / v * w < g[k] - (k - j) / v * w) tt--; // If the tail element is less than or equal to g[k]-(k-j)/v*w, the queue will be eliminated q[++tt] = k; // Remember to join the team } } } cout << f[V] << endl; return 0; }
Group Backpack
First enumerate the item group, then enumerate the volume from large to small, and enumerate the decisions within the group
General meaning:
There are \ (N \) groups of items and a backpack with a capacity of \ (V \). There are several items in each group. At most one item in the same group can be selected. The volume of each item is \ (V {ij} \) and the value is \ (w {ij} \), where \ (i \) is the group number and \ (j \) is the number in the group. Solve which items are loaded into the backpack, so that the total volume of the items does not exceed the capacity of the backpack, and the total value is the largest.
Status representation: \ (f[i][j] \) represents the top \ (I \) groups, and the volume does not exceed the maximum value of all schemes selected
Status calculation: \ (f[i][j] = max(f[i - 1][j], f[i - 1][j - v_{ik} + w_{ik}]) \)
for(int i = 1; i <= n; i++) // Enumerate item groups for(int j = m; j >= 0; j--) // Enumeration volume for(int k = 0; k < s[i]; k++) // Enumerate intra group allocation schemes if(v[i][k] <= j) // If you can put it in your backpack f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
State machine model DP
- Establish a state machine model, open more one-dimensional to represent the state, and the states can transfer to each other
State compression DP
Chessboard model
main points
- For the problem of legal placement, from the point of view of the line, whether the current line can be placed is related to the placement state of the previous line. The problem can be solved by binary compression combined with bit operation
Example: artillery position
#define pb push_back const int N = 110, M = 1 << 10; int n, m, cnt[M], g[N], f[2][M][M]; vector<int> state; // In order to place in which behavior decision-making stage, there is not enough space, and the rolling array optimization only uses the i - 1 line state, which is taken directly& // Status indication: f[i][j][k], which indicates the maximum number of places currently in line I, line I is j, and line i - 1 is K // State calculation: F [I & 1] [b] [a] = max (f [I & 1] [b] [a], f [(I - 1) & 1] [a] [C] + CNT [b]); bool check(int x){ for(int i = 0; i < m; i++) if( x >> i & 1 && (x >> (i + 1) & 1 || x >> (i + 2) & 1)) // 1. 4 adjacent positions cannot have 1 return false; return true; } int count(int x){ int res = 0; for(int i = 0; i < m; i++) if(x >> i & 1) res++; return res; } int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) for(int j = 0; j < m; j++){ char c; cin >> c; if(c == 'H') g[i] += 1 << j; } for(int i = 0; i < 1 << m; i++) if(check(i)){ state.pb(i); cnt[i] = count(i); } for(int i = 1; i <= n + 2; i++){ for(int j = 0; j < state.size(); j++) for(int k = 0; k < state.size(); k++) for(int u = 0; u < state.size(); u++){ int a = state[k], b = state[j], c = state[u]; // a: Line i-1, b: line i, c: Line i-2 if(a & b | b & c | a & c) continue; // Three states cannot be transferred if(a & g[i - 1] | b & g[i]) continue; // It cannot be placed on the mountain f[i & 1][b][a] = max(f[i & 1][b][a], f[(i - 1) & 1][a][c] + cnt[b]); } } cout << f[(n + 2) & 1][0][0] << endl; return 0; }
Binary compression idea
Example: Hamilton path
Given a complete graph, find the shortest path through all points from 0 to n-1
const int N = 20, M = 1 << 20; int f[M][N], w[N][N]; int n; int hamilton(){ memset(f, 0x3f, sizeof f); f[1][0] = 0; // f[i][j] status is I, staying at the shortest path of point j for(int i = 1; i < 1 << n; i++) // Enumerate all States for(int j = 0; j < n; j++) // At which point in the enumeration state if(i >> j & 1) // There's this point for(int k = 0; k < n; k++) // Enumerate from which point to move to j if((i ^ 1 << j) >> k & 1) // There is k this point f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + w[k][j]); // state transition return f[(1 << n) - 1][n - 1]; // After all the points, I finally stay at the point of n - 1 } int main(){ cin >> n; for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) cin >> w[i][j]; cout << hamilton() << endl; return 0; }
Set model
To be added
Interval DP
main points
- Taking the interval length as the decision-making stage
One dimensional interval DP
Some skills
- Circular interval, you can copy the interval at the back, break the ring and form a chain
Example: Ring Stone merging
const int N = 410, INF = 0x3f3f3f3f; int w[N], n, s[N]; int f[N][N], g[N][N]; // Two identical chains simulate rings // Status representation: f[l][r] represents the minimum consolidation cost from interval l to interval r // State transition: f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); int main(){ cin >> n; for(int i = 1; i <= n; i ++){ cin >> w[i]; w[i + n] = w[i]; } memset(f, 0x3f, sizeof f); memset(g, -0x3f, sizeof g); for(int i = 1; i <= 2 * n; i++) s[i] = s[i - 1] + w[i]; for(int len = 1; len <= n; len++) for(int l = 1; l + len - 1 <= 2 * n; l++){ int r = l + len - 1; if(r > 2 * n) break; if(len == 1) f[l][r] = g[l][r] = 0; else for(int k = l; k <= r; k++){ f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]); } } int max_ = -INF, min_ = INF; for(int i = 1; i <= n; i++){ max_ = max(max_, g[i][i + n - 1]); // For the interval with length n, the endpoint difference is len - 1 min_ = min(min_, f[i][i + n - 1]); } cout << min_ << endl << max_; return 0; }
Two dimensional interval DP
Example: chessboard segmentation
#include<iomanip> const int N = 9, M = 16; double f[N][N][N][N][M], X; int n, g[N][N], s[N][N]; // Two dimensional interval DP // The upper and lower vertices of the rectangle (yx1] [YK) represent the minimum variance, and the upper and lower vertices of the rectangle (yx1] [YK) represent the minimum variance // State calculation: dividing line of circular cutting (horizontal and vertical and which part to continue cutting) int get_sum(int x1, int y1, int x2, int y2){ return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]; } double get(int x1, int y1, int x2, int y2){ double sum = get_sum(x1, y1, x2, y2) - X; return sum * sum / n; } double dp(int x1, int y1, int x2, int y2, int k){ double& v = f[x1][y1][x2][y2][k]; if(v >= 0) return v; if(k == 1) return get(x1, y1, x2, y2); v = 1e9; for(int i = x1; i < x2; i++){ v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2)); v = min(v, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2)); } for(int i = y1; i < y2; i++){ v = min(v, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2)); v = min(v, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i)); } return v; } int main(){ cin >> n; for(int i = 1; i <= 8; i++) for(int j = 1; j <= 8; j++){ cin >> g[i][j]; s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + g[i][j]; } memset(f, -1, sizeof f); X = s[8][8] * 1.0 / n; cout << fixed << setprecision(3) << sqrt(dp(1, 1, 8, 8, n)) << endl; return 0; }
Tree DP
main points
- A set is represented by a point
Find the diameter of the tree
const int N = 1e4 + 10, M = 2 * N; int n, idx, e[M], ne[M], h[N], w[M], ans; // Status representation: a point represents the maximum length of all paths with this point as the highest point void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dfs(int u, int fa){ // Bring in the parent node number to ensure that the search is from top to bottom int d1 = 0, d2 = 0; // The path from the largest and second largest to the leaf node for(int i = h[u]; ~i; i = ne[i]){ int j = e[i]; if(j == fa) continue; int d = dfs(j, u) + w[i]; if(d >= d1) d2 = d1, d1 = d; else if(d > d2) d2 = d; } ans = max(ans, d1 + d2); return d1; // Returns the longest path to the leaf node } int main(){ cin >> n; memset(h, -1, sizeof h); for(int i = 0; i < n - 1; i++){ int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } dfs(1, -1); cout << ans << endl; return 0; }
Change root DP
main points
- Think about whether the parent node updates the child node or the child node updates the parent node information
Find the center of the tree by changing the root DP
- The center of the tree: the farthest and closest point to other nodes in the tree
const int N = 1e4 + 10, M = 2 * N, INF = 0x3f3f3f3f; int h[N], e[M], ne[M], w[M], idx; int n, d1[N], d2[N], up[N], p1[N], p2[N]; // Find the length of each node up or down (all directions) to the edge, enumerate all paths of all points, and find the center of the tree void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dfs_up(int u, int fa){ // Search from top to bottom, and update the parent node information from the child node information d1[u] = d2[u] = -INF; for(int i = h[u]; ~i; i = ne[i]){ int j = e[i]; if(j == fa) continue; int d = dfs_up(j, u) + w[i]; if(d >= d1[u]){ d2[u] = d1[u], d1[u] = d; p2[u] = p1[u], p1[u] = j; } else if(d > d2[u]) d2[u] = d, p2[u] = j; } if(d1[u] == -INF) d1[u] = d2[u] = 0; return d1[u]; } void dfs_down(int u, int fa){ // It still searches from top to bottom, but updates the child node information from the parent node information for(int i = h[u]; ~i; i = ne[i]){ int j = e[i]; if(j == fa) continue; if(p1[u] == j) up[j] = w[i] + max(up[u], d2[u]); // The longest downward path of the parent node passes through this point and is updated by the second longest path else up[j] = w[i] + max(up[u], d1[u]); // Otherwise, it is updated by the longest path dfs_down(j, u); } } int main(){ cin >> n; memset(h, -1, sizeof h); for(int i = 0; i < n - 1; i ++){ int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } dfs_up(1, -1); dfs_down(1, -1); int res = INF; for(int i = 1; i <= n; i++) res = min(res, max(d1[i], up[i])); cout << res << endl; return 0; }
Digital DP
main points
- Think about digital DP in the form of tree (think about digital)
- Query the number of legal numbers in \ ([l, r] \), and use the prefix and ideas dp(r) - dp(l - 1), dp(x) to find the legal numbers in \ ([1,x] \)
State representation
- \(f[i][j] \) indicates the number of schemes with the highest bit of \ (j \) and \ (I \) bits
- \(f[i][j][k] \), indicating that there are \ (I \) bits, the highest bit is \ (j \), and the result of the sum of all bits \ (\% p \) is the number of digits of \ (K \)
- \(f[i][j][a][b] \), there are \ (I \) bits, the highest bit is \ (j \), digit sum \ (\% 7=a \), value \ (\% 7=b \), and the number of legal schemes
Cyclic iteration
Example: number of degrees
- Find out how many numbers meet \ (1 \) in the base of \ (k \) and \ (B \)
int K, B, l, r; int C[N][N]; // Think about digital DP in the form of tree (think about digital) void init(){ for(int i = 0; i < N; i++) for(int j = 0; j <= i; j++) if(!j) C[i][j] = 1; else C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; } int dp(int n){ if(!n) return 0; // Step 1: judge the boundary vector<int> v; while(n){ // Step 2: digital conversion v.push_back(n % B); n /= B; } int res = 0, last = 0; // Part III: define the answer res and the number of 1 in B-ary last for(int i = v.size() - 1; i >= 0; i--){ // Enumerate each bit from high to low int x = v[i]; if(x){ // If x is 0, there is no value for discussion. Continue to the next discussion res += C[i][K - last]; // If x > 0, this bit can be taken as 0, followed by K-last 1 if(x > 1){ // X > 1, this bit can also take 1, and the following i bit can take K-last-1 1 if(K - last - 1 >= 0) res += C[i][K - last - 1]; break; // Because the branch on the right is greater than 1, and the topic requires only 0 or 1, the right is illegal and will not continue the discussion } else{ // x = 1. To record last + +, it means that 1 must be taken last++; if(last > K) break; } } if(!i && last == K) // At the end, there is a last 1 on the digit. In special cases, res++ res++; } return res; } int main(){ cin >> l >> r >> K >> B; init(); cout << dp(r) - dp(l - 1) << endl; return 0; }
Memory search
Monotone queue optimization DP
- Monotone queue can only save the information of points that have passed
Example: beacon fire transmission
using namespace std; const int N = 2e5 + 10; int f[N], w[N], n, m, q[N]; // Status indication: f[i] indicates that the first i - 1 beacon has been transmitted and the minimum cost of lighting the i-th beacon (this definition can have no aftereffect) // State calculation: F [i] = min (f [J]) + w [i] (I - M < = J < I - 1), optimized with monotone queue int main(){ cin >> n >> m; for(int i = 1; i <= n; i++) cin >> w[i]; int hh = 0, tt = 0; for(int i = 1; i <= n; i++){ if(hh <= tt && q[hh] < i - m) hh++; f[i] = f[q[hh]]+ w[i]; while(hh <= tt && f[q[tt]] >= f[i]) tt--; q[++tt] = i; } while(hh <= tt && q[hh] < n - m + 1) hh++; // Tip: the answer to moving the queue back one bit is min(f[n - m + 1, n]) cout << f[q[hh]] << endl; return 0; }
2D sliding window
Output only one integer, which is \ (a) × b) all "\ (n) in the matrix × N \) the minimum value of the difference between the maximum integer and the minimum integer in the square area.
Example: an ideal square
const int N = 1010; int w[N][N], row_min[N][N], row_max[N][N], q[N], n, k, m; // Idea: discuss the minimum value and the maximum value. Similarly, first make a monotonic queue record for each line. Each point is the right end point of the window with the length of k, and record the minimum value of the window // The record results of rows are monotonically queued on the column. For k rows, the number at all points after k columns is the maximum value in the matrix of k * k // Get the minimum value of a sliding window and b[] access the results void get_min(int a[], int b[], int len){ int hh = 0, tt = 0; for(int i = 1; i <= len; i++){ if(hh <= tt && q[hh] <= i - k) hh++; while(hh <= tt && a[q[tt]] >= a[i]) tt--; q[++tt] = i; b[i] = a[q[hh]]; } } // Get the maximum value of a sliding window and b[] access the results void get_max(int a[], int b[], int len){ int hh = 0, tt = 0; for(int i = 1; i <= len; i++){ if(hh <= tt && q[hh] <= i - k) hh++; while(hh <= tt && a[q[tt]] <= a[i]) tt--; q[++tt] = i; b[i] = a[q[hh]]; } } int main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); cin >> n >> m >> k; for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) cin >> w[i][j]; for(int i = 1; i <= n; i++){ get_min(w[i], row_min[i], m); get_max(w[i], row_max[i], m); } int a[N], b[N], c[N], res = 1e9 + 10; for(int i = k; i <= m; i++){ for(int j = 1; j <= n; j++) a[j] = row_min[j][i]; get_min(a, b, n); for(int j = 1; j <= n; j++) a[j] = row_max[j][i]; get_max(a, c, n); for(int j = k; j <= n; j++) res = min(res, c[j] - b[j]); } cout << res << endl; return 0; }
Slope optimization DP
- Slope optimization generally requires binary / CDQ / balance tree optimization
Basic convex hull optimization
Example: task arrangement
There are \ (n \) tasks arranged in a sequence, and the order shall not be changed. The time consumption of the \ (I \) task is \ (T_i \), and the cost coefficient is \ (C_i \)
Now you need to divide the \ (n \) tasks into several batches for processing
The segment head of each batch requires an additional \ (S \) time to start the machine. The completion time of each task is the end time of the batch.
The cost of completing a task is: the time from \ (0 \) to the end of the batch of the task \ (t \) multiplied by the cost coefficient of the task \ (C \)
- Using the idea of cost advance
const int N = 3e5 + 10; ll f[N], q[N], t[N], c[N], n, s; // f[i] represents the minimum cost of performing task I // 1. Slope = t[i] + s monotonically increasing // 2. Abscissa c[i] monotonically increasing // 3. f[i] on the intercept, find the point with the largest intercept and consider convex hull optimization. Finding the first point with a slope greater than k is the answer. Due to monotonicity, monotone queue optimization is adopted to O(n) int main(){ cin >> n >> s; for(int i = 1; i <= n; i++){ cin >> t[i] >> c[i]; t[i] += t[i - 1], c[i] += c[i - 1]; } int hh = 0, tt = 0; q[0] = 0; for(int i = 1; i <= n; i++){ // Queue hh, tt bring q [] while(hh < tt && (f[q[hh + 1]] - f[q[hh]]) < (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh++; int j = q[hh]; f[i] = f[j] + t[i] * (c[i] - c[j]) + s * (c[n] - c[j]); while(hh < tt && (f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]]) < (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]])) tt--; q[++tt] = i; } cout << f[n] << endl; return 0; }
Miscellaneous items such as IO
Unbinding of cin and cout
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cout precision control
cout << fixed << setprecision(5) << 1.2 << endl;//The output is 1.20000
C + + optimization
\(O2 \) optimization
#pragma GCC optimize(2)
\(O3 \) optimization
#pragma GCC optimize(3,"Ofast","inline")
Python 3 multiline input
while True: try: m = int(input().strip()) except EOFError: break `