boolcheck(intx){/* ... */}// Checks whether x satisfies some property
// When the interval [l, r] is divided into [l, mid] and [mid + 1, r]:
intbsearch_1(intl,intr){while(l<r){intmid=l+r>>1;if(check(mid))r=mid;// check() determines if mid meets the criteria
elsel=mid+1;}returnl;}// When the interval [l, r] is divided into [l, mid - 1] and [mid, r]:
intbsearch_2(intl,intr){while(l<r){intmid=l+r+1>>1;if(check(mid))l=mid;elser=mid-1;}returnl;}
boolcheck(doublex){/* ... */}// Checks whether x satisfies some property
doublebsearch_3(doublel,doubler){constdoubleeps=1e-6;// eps represents precision, depending on the problem's requirements
while(r-l>eps){doublemid=(l+r)/2;if(check(mid))r=mid;elsel=mid;}returnl;}
// C = A + B, A >= 0, B >= 0
vector<int>add(vector<int>&A,vector<int>&B){if(A.size()<B.size())returnadd(B,A);vector<int>C;intt=0;for(inti=0;i<A.size();i++){t+=A[i];if(i<B.size())t+=B[i];C.push_back(t%10);t/=10;}
// C = A - B, provided A >= B, A >= 0, B >= 0
vector<int>sub(vector<int>&A,vector<int>&B){vector<int>C;for(inti=0,t=0;i<A.size();i++){t=A[i]-t;if(i<B.size())t-=B[i];C.push_back((t+10)%10);if(t<0)t=1;elset=0;}while(C.size()>1&&C.back()==0)C.pop_back();returnC;}
// C = A * b, A >= 0, b >= 0
vector<int>mul(vector<int>&A,intb){vector<int>C;intt=0;for(inti=0;i<A.size()||t;i++){if(i<A.size())t+=A[i]*b;C.push_back(t%10);t/=10;}while(C.size()>1&&C.back()==0)C.pop_back();returnC;}
// A / b = C ... r, A >= 0, b > 0
vector<int>div(vector<int>&A,intb,int&r){vector<int>C;r=0;for(inti=A.size()-1;i>=0;i--){r=r*10+A[i];C.push_back(r/b);r%=b;}reverse(C.begin(),C.end());while(C.size()>1&&C.back()==0)C.pop_back();returnC;}
4 Prefix and Difference
Prefix: Quickly calculate the sum of a certain segment
Difference: Quickly add a certain value +c to a segment, inverse operation of prefix
for(inti=0,j=0;i<n;i++){while(j<i&&check(i,j))j++;// logic specific to the problem
}Commonproblemcategories:1.Forasequence,usetwopointerstomaintainarange2.Fortwosequences,maintainsomekindoforder,likemergingtwoorderedsequencesinmergesort
vector<int>alls;// store all values to be discretized
sort(alls.begin(),alls.end());// sort all values
alls.erase(unique(alls.begin(),alls.end()),alls.end());// remove duplicate elements
```cpp// Use binary search to find the discretized value corresponding to x
intfind(intx)// Find the first position that is greater than or equal to x
{intl=0,r=alls.size()-1;while(l<r){intmid=l+r>>1;if(alls[mid]>=x)r=mid;elsel=mid+1;}returnr+1;// Map to 1, 2, ...n
}
// Merge all intervals with intersections
voidmerge(vector<PII>&segs){vector<PII>res;sort(segs.begin(),segs.end());intst=-2e9,ed=-2e9;for(autoseg:segs)if(ed<seg.first){if(st!=-2e9)res.push_back({st,ed});st=seg.first,ed=seg.second;}elseed=max(ed,seg.second);if(st!=-2e9)res.push_back({st,ed});segs=res;}
Data Structures
1 Linked Lists and Adjacency Lists: Storage of Trees and Graphs
// head stores the head of the list, e[] stores the value of the node, ne[] stores the next pointer of the node, idx indicates the current node in use
inthead,e[N],ne[N],idx;// Initialization
voidinit(){head=-1;idx=0;}// Insert a number a at the head of the list
voidinsert(inta){e[idx]=a,ne[idx]=head,head=idx++;}// Remove the head node, ensuring the head node exists
voidremove(){head=ne[head];}
// e[] represents the value of the node, l[] represents the left pointer of the node, r[] represents the right pointer of the node, idx indicates the current node in use
inte[N],l[N],r[N],idx;// Initialization
voidinit(){// 0 is the left endpoint, 1 is the right endpoint
r[0]=1,l[1]=0;idx=2;}// Insert a number x to the right of node a
voidinsert(inta,intx){e[idx]=x;l[idx]=a,r[idx]=r[a];l[r[a]]=idx,r[a]=idx++;}// Delete node a
voidremove(inta){l[r[a]]=l[a];r[l[a]]=r[a];}
2 Stacks and Queues: Monotonic Queues, Monotonic Stacks
// tt represents the top of the stack
intstk[N],tt=0;// Insert a number at the top of the stack
stk[++tt]=x;// Pop a number from the top of the stack
tt--;// The value at the top of the stack
stk[tt];// Check if the stack is empty, if tt > 0, it indicates it's not empty
if(tt>0){}
// hh represents the head of the queue, tt represents the tail of the queue
intq[N],hh=0,tt=-1;// Insert a number at the tail of the queue
q[++tt]=x;// Pop a number from the head of the queue
hh++;// The value at the head of the queue
q[hh];// Check if the queue is empty, if hh <= tt, it indicates it's not empty
if(hh<=tt){}
Circular queue
1
2
// hh represents the head of the queue, tt represents the position after the tail of the queue
intq[N],hh=0,tt=0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Insert a number at the end of the queue
q[tt++]=x;if(tt==N)tt=0;// Pop a number from the front of the queue
hh++;if(hh==N)hh=0;// Value at the front of the queue
q[hh];// Check if the queue is empty, if hh != tt, then it is not empty
if(hh!=tt){}
Common models: Find the maximum/minimum value in the sliding window
1
2
3
4
5
6
7
inthh=0,tt=-1;for(inti=0;i<n;i++){while(hh<=tt&&check_out(q[hh]))hh++;// Check if the head of the queue slides out of the window
while(hh<=tt&&check(q[tt],i))tt--;q[++tt]=i;}
// s[] is the long text, p[] is the pattern string, n is the length of s, m is the length of p
CalculatetheNextarrayofthepatternstring:for(inti=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
for(inti=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){j=ne[j];// Logic after a successful match
}}//The C library function strstr() works in the same way
intson[N][26],cnt[N],idx;// Node 0 is both the root node and the null node
// son[][] stores the children of each node in the tree
// cnt[] stores the number of words ending in each node
// Insert a string
voidinsert(char*str){intp=0;for(inti=0;str[i];i++){intu=str[i]-'a';if(!son[p][u])son[p][u]=++idx;p=son[p][u];}cnt[p]++;}// Query the number of occurrences of a string
intquery(char*str){intp=0;for(inti=0;str[i];i++){intu=str[i]-'a';if(!son[p][u])return0;p=son[p][u];}returncnt[p];}//It's possible to use map<string,int>, but the timing would be more than twice as slow
intp[N];//Stores the ancestor node of each point
// Return the ancestor node of x
intfind(intx){if(p[x]!=x)p[x]=find(p[x]);returnp[x];}// Initialization, assuming node numbers are 1~n
for(inti=1;i<=n;i++)p[i]=i;// Merge the two sets a and b belong to:
p[find(a)]=find(b);
(2)Union Find with size maintenance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
intp[N],size[N];//p[] stores the ancestor node of each point, size[] only has meaning for ancestor nodes, indicating the number of points in the set the ancestor node belongs to
// Return the ancestor node of x
intfind(intx){if(p[x]!=x)p[x]=find(p[x]);returnp[x];}// Initialization, assuming node numbers are 1~n
for(inti=1;i<=n;i++){p[i]=i;size[i]=1;}
1
2
3
// Merge the two sets to which a and b belong:
size[find(b)]+=size[find(a)];p[find(a)]=find(b);
(3) Union-find set maintaining the distance to the ancestor node:
```cpp
int p[N], d[N];
//p[] stores the ancestor node of each point, d[x] stores the distance from x to p[x]
// Return the ancestor node of x
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// Initialization, assuming the node numbers are 1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// Merge the two sets that a and b belong to:
p[find(a)] = find(b);
d[find(a)] = distance; // Initialize the offset of find(a) based on the specific problem
// h[N] stores the values in the heap, h[1] is the heap top, the left child of x is 2x, the right child is 2x + 1
// ph[k] stores the position in the heap of the kth inserted point
// hp[k] stores which insert order the point at index k in the heap was
inth[N],ph[N],hp[N],size;// Swap two points, and their mapping relationships
voidheap_swap(inta,intb){swap(ph[hp[a]],ph[hp[b]]);swap(hp[a],hp[b]);swap(h[a],h[b]);}voiddown(intu){intt=u;if(u*2<=size&&h[u*2]<h[t])t=u*2;if(u*2+1<=size&&h[u*2+1]<h[t])t=u*2+1;if(u!=t){heap_swap(u,t);down(t);}}voidup(intu){while(u/2&&h[u]<h[u/2]){heap_swap(u,u/2);u>>=1;}}// O(n) build heap
for(inti=n/2;i;i--)down(i);
(1)Chaininginth[N],e[N],ne[N],idx;// Insert a number into the hash table
voidinsert(intx){intk=(x%N+N)%N;e[idx]=x;ne[idx]=h[k];h[k]=idx++;}// Check if a number exists in the hash table
boolfind(intx){intk=(x%N+N)%N;for(inti=h[k];i!=-1;i=ne[i])if(e[i]==x)returntrue;returnfalse;}(2)OpenAddressinginth[N];// If x is in the hash table, return the index of x; if x is not in the hash table, return the insertion position for x
intfind(intx){intt=(x%N+N)%N;while(h[t]!=null&&h[t]!=x){t++;if(t==N)t=0;}returnt;}
Core idea: Treat the string as a base-P number, where P is usually 131 or 13331, as these values are less likely to cause collisions
Tips: Use 2^64 as the modulus. This way, direct storage with unsigned long long, and overflow result is the modulo result
1
2
3
4
5
6
7
8
9
10
typedefunsignedlonglongULL;ULLh[N],p[N];// h[k] stores the hash value of the first k letters of the string, p[k] stores P^k mod 2^64
// Initialization
p[0]=1;for(inti=1;i<=n;i++){h[i]=h[i-1]*P+str[i];p[i]=p[i-1]*P;}
1
2
3
4
5
// Calculate the hash value of the substring str[l ~ r]
ULLget(intl,intr){returnh[r]-h[l-1]*p[r-l+1];}
Trees are a special kind of graph, with similar storage methods.
For an undirected graph’s edge ab, store two directed edges a->b, b->a.
Thus, we can only consider the storage of directed graphs.
(1) Adjacency matrix: g[a][b] stores the edge a->b
(2) Adjacency list:
1
2
3
4
5
6
7
8
9
10
11
12
13
// For each point k, open a linked list, storing all points that k can reach. h[k] stores the head of this linked list
inth[N],e[N],ne[N],idx;// Add an edge a->b
voidadd(inta,intb){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}// Initialization
idx=0;memset(h,-1,sizeofh);
1.2 Traversal of Trees and Graphs
Time complexity O(n+m), n represents the number of vertices, m represents the number of edges
queue<int>q;st[1]=true;// indicates that vertex 1 has been traversed
q.push(1);while(q.size()){intt=q.front();q.pop();for(inti=h[t];i!=-1;i=ne[i]){intj=e[i];if(!st[j]){st[j]=true;// indicates that vertex j has been traversed
q.push(j);}}}
2 Traversal of Trees and Graphs Topological Sorting
booltopsort(){inthh=0,tt=-1;// d[i] stores the in-degree of vertex i
for(inti=1;i<=n;i++)if(!d[i])q[++tt]=i;while(hh<=tt){intt=q[hh++];for(inti=h[t];i!=-1;i=ne[i]){intj=e[i];if(--d[j]==0)q[++tt]=j;}}// If all vertices have been enqueued, a topological order exists; otherwise, it does not exist.
returntt==n-1;}
intg[N][N];// Stores each edge
intdist[N];// Stores the shortest distance from vertex 1 to every other vertex
boolst[N];// Stores whether the shortest path for each vertex has been determined
// Seeks the shortest path from vertex 1 to vertex n, returns -1 if it does not exist
intdijkstra(){memset(dist,0x3f,sizeofdist);dist[1]=0;for(inti=0;i<n-1;i++){intt=-1;// Among the vertices whose shortest path has not been determined, find the one with the minimum distance
for(intj=1;j<=n;j++)if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;// Use t to update the distance of other vertices
for(intj=1;j<=n;j++)dist[j]=min(dist[j],dist[t]+g[t][j]);st[t]=true;}if(dist[n]==0x3f3f3f3f)return-1;returndist[n];}
typedefpair<int,int>PII;intn;// Number of vertices
inth[N],w[N],e[N],ne[N],idx;// Adjacency list to store all edges
intdist[N];// Stores the distance of all vertices from vertex 1
boolst[N];// Stores whether the shortest distance for each vertex has been determined
// Seeks the shortest distance from vertex 1 to vertex n, returns -1 if it does not exist
intdijkstra(){memset(dist,0x3f,sizeofdist);dist[1]=0;priority_queue<PII,vector<PII>,greater<PII>>heap;heap.push({0,1});// first stores the distance, second stores the vertex number
while(heap.size()){autot=heap.top();heap.pop();intver=t.second,distance=t.first;```cppif(st[ver])continue;st[ver]=true;for(inti=h[ver];i!=-1;i=ne[i]){intj=e[i];if(dist[j]>distance+w[i]){dist[j]=distance+w[i];heap.push({dist[j],j});}}}if(dist[n]==0x3f3f3f3f)return-1;returndist[n];}
intn,m;// n represents the number of vertices, m represents the number of edges
intdist[N];// dist[x] stores the shortest path distance from 1 to x
structEdge// Edge, a is the start vertex, b is the end vertex, w is the weight of the edge
{inta,b,w;}edges[M];// Finds the shortest path distance from 1 to n; returns -1 if it's not possible to travel from 1 to n.
intbellman_ford(){memset(dist,0x3f,sizeofdist);dist[1]=0;// If the triangle inequality is still relaxed in the nth iteration, it means there exists a shortest path with length n+1. According to the pigeonhole principle, there must be at least two identical points on the path, indicating that the graph contains a negative weight cycle.
for(inti=0;i<n;i++){for(intj=0;j<m;j++){inta=edges[j].a,b=edges[j].b,w=edges[j].w;if(dist[b]>dist[a]+w)dist[b]=dist[a]+w;}}if(dist[n]>0x3f3f3f3f/2)return-1;returndist[n];}
—— Template problem AcWing 851. spfa for shortest path
Time complexity On average O(m), in the worst case O(nm), n represents the number of vertices, m represents the number of edges
intn;// Total number of vertices
inth[N],w[N],e[N],ne[N],idx;// Adjacency list to store all edges
intdist[N];// Stores the shortest distance from vertex 1 to each vertex
boolst[N];// Stores whether each vertex is in the queue
// Finds the shortest path distance from vertex 1 to n; returns -1 if it's not possible to walk from 1 to n.
intspfa(){memset(dist,0x3f,sizeofdist);dist[1]=0;queue<int>q;q.push(1);st[1]=true;while(q.size()){autot=q.front();q.pop();st[t]=false;for(inti=h[t];i!=-1;i=ne[i]){intj=e[i];if(dist[j]>dist[t]+w[i]){dist[j]=dist[t]+w[i];if(!st[j])// If j is already in the queue, there's no need to insert j again
{q.push(j);st[j]=true;}}}}if(dist[n]==0x3f3f3f3f)return-1;returndist[n];}
3.5 spfa Algorithm to Detect Negative Cycles in Graphs
intn;// Total number of vertices
inth[N],w[N],e[N],ne[N],idx;// Adjacency list to store all edges
intdist[N],cnt[N];// dist[x] stores the shortest distance from vertex 1 to x, cnt[x] stores the number of vertices passed through from 1 to x on the shortest path
boolst[N];// Stores whether each vertex is in the queue
// If there exists a negative cycle, returns true; otherwise, returns false.
boolspfa(){// No need to initialize dist array
// Principle: If there are n vertices on a certain shortest path (excluding oneself), then including oneself, there is a total of n+1 vertices. According to the pigeonhole principle, there must be two identical vertices, therefore a cycle exists.
queue<int>q;for(inti=1;i<=n;i++){q.push(i);st[i]=true;}while(q.size()){autot=q.front();q.pop();st[t]=false;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for(inti=h[t];i!=-1;i=ne[i]){intj=e[i];if(dist[j]>dist[t]+w[i]){dist[j]=dist[t]+w[i];cnt[j]=cnt[t]+1;if(cnt[j]>=n)returntrue;// If the shortest path from node 1 to x contains at least n nodes (excluding itself), then it implies the existence of a cycle
if(!st[j]){q.push(j);st[j]=true;}}}}returnfalse;}
Initialization:for(inti=1;i<=n;i++)for(intj=1;j<=n;j++)if(i==j)d[i][j]=0;elsed[i][j]=INF;// After the algorithm finishes, d[a][b] represents the shortest distance from a to b.
voidfloyd(){for(intk=1;k<=n;k++)for(inti=1;i<=n;i++)for(intj=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);}
intn;// n represents the number of vertices
intg[N][N];// Adjacency matrix, storing all edges
intdist[N];// Stores the distance from other vertices to the current minimum spanning tree
boolst[N];// Stores whether each vertex is already in the spanning tree
// If the graph is not connected, it returns INF(value is 0x3f3f3f3f), otherwise it returns the sum of the tree edge weights of the minimum spanning tree.
intprim(){memset(dist,0x3f,sizeofdist);intres=0;for(inti=0;i<n;i++){intt=-1;for(intj=1;j<=n;j++)if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;if(i&&dist[t]==INF)returnINF;if(i)res+=dist[t];st[t]=true;for(intj=1;j<=n;j++)dist[j]=min(dist[j],g[t][j]);}returnres;}
intn,m;// n is the number of vertices, m is the number of edges
intp[N];// The parent array of the disjoint set union
structEdge// To store edges
{inta,b,w;booloperator<(constEdge&W)const{returnw<W.w;}}edges[M];intfind(intx)// Core operation of the disjoint set union
{if(p[x]!=x)p[x]=find(p[x]);returnp[x];}intkruskal(){sort(edges,edges+m);for(inti=1;i<=n;i++)p[i]=i;// Initialize the disjoint set union
intres=0,cnt=0;for(inti=0;i<m;i++){inta=edges[i].a,b=edges[i].b,w=edges[i].w;a=find(a),b=find(b);if(a!=b)// If the two connected components are not connected, then merge these two components
{p[a]=b;res+=w;cnt++;}}
intn;// n indicates the number of vertices
inth[N],e[M],ne[M],idx;// Adjacency list to store the graph
intcolor[N];// Indicates the color of each vertex, -1 means uncolored, 0 means white, 1 means black
// Parameters: u indicates the current node, c indicates the color of the current node
booldfs(intu,intc){color[u]=c;for(inti=h[u];i!=-1;i=ne[i]){intj=e[i];if(color[j]==-1){if(!dfs(j,!c))returnfalse;}elseif(color[j]==c)returnfalse;}returntrue;}boolcheck(){memset(color,-1,sizeofcolor);boolflag=true;for(inti=1;i<=n;i++)if(color[i]==-1)if(!dfs(i,0)){flag=false;break;}returnflag;}
intn1,n2;// n1 indicates the number of vertices in the first set, n2 indicates the number of vertices in the second set
inth[N],e[M],ne[M],idx;// Adjacency list stores all edges, in the Hungarian algorithm, only edges from the first set to the second set are used, so here it only needs to store one direction of the edges
intmatch[N];// Stores which vertex in the first set is currently matched with each vertex in the second set
boolst[N];// Indicates whether each vertex in the second set has been traversed
boolfind(intx){for(inti=h[x];i!=-1;i=ne[i]){intj=e[i];if(!st[j]){st[j]=true;if(match[j]==0||find(match[j])){match[j]=x;returntrue;}}}returnfalse;}// To find the maximum matching number, enumerate each vertex in the first set to see if it can match a vertex in the second set
intres=0;for(inti=1;i<=n1;i++){memset(st,false,sizeofst);if(find(i))res++;}
intprimes[N],cnt;// primes[] stores all prime numbers
boolst[N];// st[x] stores if x has been sieved 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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
### Linear Sieve Algorithm for Finding Prime Numbers
—— Template question [AcWing 868. Sieve of Eratosthenes](https://www.acwing.com/problem/content/870/)
```cpp
int primes[N], cnt; // primes[] stores all the prime numbers
bool st[N]; // st[x] indicates whether x has been sieved
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[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
intprimes[N],cnt;// primes[] stores all the prime numbers
inteuler[N];// stores Euler's Totient Function for each number
boolst[N];// st[x] indicates whether x has been sieved
// a[N][N] is the augmented matrix
intgauss(){intc,r;for(c=0,r=0;c<n;c++){intt=r;for(inti=r;i<n;i++)// Find the row with the maximum absolute value
if(fabs(a[i][c])>fabs(a[t][c]))t=i;if(fabs(a[t][c])<eps)continue;for(inti=c;i<=n;i++)swap(a[t][i],a[r][i]);// Swap the row with the maximum absolute value to the top
for(inti=n;i>=c;i--)a[r][i]/=a[r][c];// Make the first element of the current row 1
for(inti=r+1;i<n;i++)// Use the current row to make all the elements of the column below 0
if(fabs(a[i][c])>eps)for(intj=n;j>=c;j--)a[i][j]-=a[r][j]*a[i][c];r++;}if(r<n){for(inti=r;i<n;i++)if(fabs(a[i][n])>eps)return2;// No solution
return1;// Infinitely many solutions
}for(inti=n-1;i>=0;i--)for(intj=i+1;j<n;j++)a[i][n]-=a[i][j]*a[j][n];
// c[a][b] represents the number of ways to choose b apples from a apples
for(inti=0;i<N;i++)for(intj=0;j<=i;j++)if(!j)c[i][j]=1;elsec[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
7.2 Calculating combinations by pre-processing the inverse
—— Template problem AcWing 886. Calculate Combinations II
First, pre-process the remainder of all factorials modulo fact[N], and the inverse elements of all factorials modulo infact[N]
If the modulo number is a prime, the inverse element can be calculated using Fermat’s little theorem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
intqmi(inta,intk,intp)// Quick power template
{intres=1;while(k){if(k&1)res=(LL)res*a%p;a=(LL)a*a%p;k>>=1;}returnres;}// Pre-process the remainder of factorials and the remainder of factorial inverses
fact[0]=infact[0]=1;for(inti=1;i<N;i++){fact[i]=(LL)fact[i-1]*i%mod;infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;}
// If p is a prime, then for any integer 1 <= m <= n, we have:
C(n,m)=C(n%p,m%p)*C(n/p,m/p)(modp)intqmi(inta,intk,intp)// Quick power template
{intres=1%p;while(k){if(k&1)res=(LL)res*a%p;a=(LL)a*a%p;k>>=1;}returnres;}intC(inta,intb,intp)// Calculate combination number C(a, b) using the theorem
{if(a<b)return0;LLx=1,y=1;// x is the numerator, y is the denominator
for(inti=a,j=1;j<=b;i--,j++){x=(LL)x*i%p;y=(LL)y*j%p;}returnx*(LL)qmi(y,p-2,p)%p;}intlucas(LLa,LLb,intp){if(a<p&&b<p)returnC(a,b,p);return(LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;}
7.4 Prime factorization method to calculate combinations
—— Template problem AcWing 888. Calculate Combinations IV
When we need to calculate the actual value of a combination, rather than the remainder modulo some number, the method of prime factorization is more useful:
Use the sieve method to find all primes within a range
Calculate the power of each prime factor through the formula C(a, b) = a! / b! / (a - b)!. The power of p in n! is n / p + n / p^2 + n / p^3 + …
Use high-precision multiplication to multiply all the prime factors
intprimes[N],cnt;// Stores all primes
intsum[N];// Stores the count for each prime
boolst[N];// Indicates whether a number has been sieved out
voidget_primes(intn)// Linear sieve method for finding primes
{for(inti=2;i<=n;i++){if(!st[i])primes[cnt++]=i;for(intj=0;primes[j]<=n/i;j++){st[primes[j]*i]=true;if(i%primes[j]==0)break;}}}intget(intn,intp)// Calculate the frequency of p in n!
{intres=0;while(n){res+=n/p;n/=p;}returnres;}vector<int>mul(vector<int>a,intb)// High precision multiplication by low precision template
{vector<int>c;intt=0;for(inti=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;}returnc;}get_primes(a);// Preprocess all primes within the range
for(inti=0;i<cnt;i++)// Calculate the frequency of each prime factor
{intp=primes[i];sum[i]=get(a,p)-get(b,p)-get(a-b,p);}vector<int>res;res.push_back(1);for(inti=0;i<cnt;i++)// Multiply all prime factors using high precision multiplication
for(intj=0;j<sum[i];j++)res=mul(res,primes[i]);
Given n zeros and n ones, they are arranged in a sequence of length 2n in some order, such that for any prefix the number of zeros is not less than the number of ones, the number of such sequences is: Cat(n) = C(2n, n) / (n + 1)
NIM game —— Template for AcWing 891. Nim Game
Given N piles of items, the i-th pile has Ai items. Two players take turns acting, each time they can choose a pile and take away any number of items, possibly emptying the pile, but cannot not take any item. The player who takes the last item wins. Assuming both players adopt the optimal strategy, the question is whether the first player is guaranteed to win.
This kind of game is called NIM. The statuses faced during the game process are called positions. The player who acts first in the entire game is called the first player, and the player who acts second is called the second player. If under a certain position, whatever action is taken, the game is lost, then that position is called a losing position.
The so-called optimal strategy means that if there exists some action under a certain position that leads to the opponent facing a losing position after the action, then prioritize that action. Similarly, such positions are called winning positions. The game theory problems we discuss generally consider only the ideal situation, that is, both players make no mistakes and act with the best strategy to determine the outcome of the game.
There are no ties in NIM, only situations where the first mover is assured to win or is assured to lose.
Theorem: In the NIM game, the first player is guaranteed to win if and only if A1 ^ A2 ^ … ^ An != 0
8.2 Impartial Combinatorial Games (ICG)
A game is considered an impartial combinatorial game if it satisfies:
Two players take turns to move;
At any moment in the game, the legal moves available do not depend on which player’s turn it is;
The player who cannot move loses;
NIM game is an impartial combinatorial game, but strategic board games such as Go are not, because in Go, the battling sides can only place black or white stones, respectively, and the victory judgement is more complex, failing to meet conditions 2 and 3.
8.3 Directed Graph Games
Given a directed acyclic graph with a unique starting point where a piece is placed. Players take turns to move this piece along the directed edges, moving one step at a time, and the player who cannot move loses. This game is known as a directed graph game.
Any impartial combinatorial game can be transformed into a directed graph game. The specific method is to view each game state as a node in the graph, and to connect directed edges from each state to the next states that can be reached by legal moves.
8.4 Mex Operation
Let S be a set of non-negative integers. Define mex(S) as the operation of finding the smallest non-negative integer not in set S, i.e.:
mex(S) = min{x}, x belongs to natural numbers, and x ∉ S
8.5 SG Function
In a directed graph game, for any node x, assume there are k directed edges starting from x, leading to nodes y1, y2, …, yk, define SG(x) as the result of performing mex(S) operation on the set of SG function values of the successor nodes y1, y2, …, yk, i.e.:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
Specifically, the SG function value of the entire directed graph game G is defined as the SG function value of the starting point s of the directed graph game, i.e., SG(G) = SG(s).
8.6 Sum of Directed Graph Games
——— Template Problem AcWing 893. Collection-Nim game
Suppose G1, G2, …, Gm are m directed graph games. Define a directed graph game G, which allows the action of choosing any directed graph game Gi and moving one step in Gi. G is called the sum of the directed graph games G1, G2, …, Gm.
The SG function value of the sum of directed graph games equals the XOR sum of the SG function values of each included sub-game, i.e.:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
Theorem
A position in a directed graph game is winning if and only if the SG function value of the corresponding node is greater than 0.
A position in a directed graph game is losing if and only if the SG function value of the corresponding node equals 0.
Time Complexity and Algorithm Choice for Different Data Ranges
In general, the time limit for ACM or written exam problems is 1 second or 2 seconds. In such cases, the best practice is to control the operation count in C++ code within $10^7$∼$10^8$. Below, time complexity and algorithm choices are provided for different data ranges:
$n≤30$, exponential, dfs+pruning, state compression dp
$n≤1000000=>O(n)$, and algorithms with small constants O(nlogn), monotonic queue, hash, two pointers, disjoint set, KMP, AC automaton, small constant O(nlogn) methods: sort, binary indexed tree, heap, dijkstra, spfa
$n≤10000000=>O(n)$, two pointers, KMP, AC automaton, linear sieve for primes
$n≤10^9=>O(\sqrt n)$, prime number judgement
$n≤10^{18}=>O(logn)$, greatest common divisor, fast power, digit DP