boolcheck(intx){/* ... */}// Check if x satisfies a certain 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 satisfies the property
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){/* ... */}// Check if x satisfies a certain property
doublebsearch_3(doublel,doubler){constdoubleeps=1e-6;// eps represents the precision, depending on the precision requirement of the problem
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;}if(t)C.push_back(t);returnC;}
// C = A - B, satisfying 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;}
3.3 High Precision Multiplication by Low Precision
// 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 find the sum of a segment
Difference: Quickly add +c to a segment, inverse operation of prefix
for(inti=0,j=0;i<n;i++){while(j<i&&check(i,j))j++;// Logic for specific problem
}Commonproblemcategories:(1)Forasequence,maintainanintervalwithtwopointers(2)Fortwosequences,maintainacertainorder,suchasmergingtwoorderedsequencesinmergesort
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
// Binary search to find the discretized value corresponding to x
intfind(intx)// Find the first position 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 that have 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 List and Adjacency List: Storage of Trees and Graphs
// head stores the head of the linked list, e[] stores the value of the node, ne[] stores the next pointer of the node, idx indicates which node is currently used
inthead,e[N],ne[N],idx;// Initialization
voidinit(){head=-1;idx=0;}// Insert a number a at the head of the linked list
voidinsert(inta){e[idx]=a,ne[idx]=head,head=idx++;}// Delete the head node, need to ensure 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 which node is currently used
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 Stack and Queue: Monotonic Queue, Monotonic Stack
// tt indicates the top of the stack
intstk[N],tt=0;// Insert a number to the top of the stack
stk[++tt]=x;// Pop a number from the top of the stack
tt--;// Value at the top of the stack
stk[tt];// Check if the stack is empty, if tt > 0, it means not empty
if(tt>0){}
// hh indicates the head of the queue, tt indicates the tail of the queue
intq[N],hh=0,tt=-1;// Insert a number to the tail of the queue
q[++tt]=x;// Pop a number from the head of the queue
hh++;// Value at the head of the queue
q[hh];// Check if the queue is empty, if hh <= tt, it means not empty
if(hh<=tt){}
Circular Queue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// hh indicates the head of the queue, tt indicates the position after the tail of the queue
intq[N],hh=0,tt=0;// Insert a number to the tail of the queue
q[tt++]=x;if(tt==N)tt=0;// Pop a number from the head of the queue
hh++;if(hh==N)hh=0;// Value at the head of the queue
q[hh];// Check if the queue is empty, if hh != tt, it means not empty
if(hh!=tt){}
2.3 Monotonic Stack
—— Template Problem AcWing 830. Monotonic Stack
Common model: Find the nearest number greater/smaller than each number on the left
—— Template Problem AcWing 154. Sliding Window
Common model: 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
ComputetheNextarrayofthepatternstring: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 successful match
}}//C's library function strstr() is like this
intson[N][26],cnt[N],idx;// Node 0 is both the root node and the empty node
// son[][] stores the child nodes of each node in the tree
// cnt[] stores the number of words ending at 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];}//You can use map<string,int>, but the time is more than twice
intp[N];// Store 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 where a and b are located:
p[find(a)]=find(b);
intp[N],size[N];//p[] stores the ancestor node of each point, size[] is only meaningful for ancestor nodes, indicating the number of points in the set where the ancestor node is located
// 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;}// Merge the two sets where a and b are located:
size[find(b)]+=size[find(a)];p[find(a)]=find(b);
(3) Union-Find maintaining distance to ancestor node:
intp[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
intfind(intx){if(p[x]!=x){intu=find(p[x]);d[x]+=d[p[x]];p[x]=u;}returnp[x];}// Initialization, assuming node numbers are 1~n
for(inti=1;i<=n;i++){p[i]=i;d[i]=0;}// Merge the two sets where a and b are located:
p[find(a)]=find(b);d[find(a)]=distance;// Initialize the offset of find(a) according to the specific problem
// h[N] stores the values in the heap, h[1] is the top of the heap, the left child of x is 2x, the right child is 2x + 1
// ph[k] stores the position of the k-th inserted point in the heap
// hp[k] stores the k-th point in the heap is the k-th inserted
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)ChainingMethodinth[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++;}// Query whether 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)OpenAddressingMethodinth[N];// If x is in the hash table, return the index of x; if x is not in the hash table, return the position where x should be inserted
intfind(intx){intt=(x%N+N)%N;while(h[t]!=null&&h[t]!=x){t++;if(t==N)t=0;}returnt;}
7.2 String Hash
—— Template Problem AcWing 841. String Hash
The core idea: Treat the string as a P-base number, the empirical value of P is 131 or 13331, the probability of conflict with these two values is low
A small trick: Use 2^64 as the modulus, so that it can be directly stored with unsigned long long, and the result of overflow is the result of modulus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;}// Calculate the hash value of the substring str[l ~ r]
ULLget(intl,intr){returnh[r]-h[l-1]*p[r-l+1];}
A tree is a special kind of graph, and the storage method is the same as that of a graph.
For an undirected graph with edge ab, store two directed edges a->b, b->a.
Therefore, we can only consider the storage of directed graphs.
(1) Adjacency Matrix: g[a][b] stores edge a->b
(2) Adjacency List:
1
2
3
4
5
6
7
8
9
10
11
12
// For each point k, open a singly linked list to store all points that k can reach. h[k] stores the head node of this singly 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 points, m represents the number of edges
(1) Depth-First Traversal —— Template Problem AcWing 846. Tree’s Center
1
2
3
4
5
6
7
8
9
10
intdfs(intu){st[u]=true;// st[u] indicates that point u has been traversed
for(inti=h[u];i!=-1;i=ne[i]){intj=e[i];if(!st[j])dfs(j);}}
queue<int>q;st[1]=true;// Indicates that point 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 point 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 point 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 points are enqueued, it means there is a topological sequence; otherwise, there is no topological sequence.
returntt==n-1;}
3 Shortest Path
3.1 Naive Dijkstra Algorithm
—— Template Problem AcWing 849. Dijkstra Shortest Path I
Time complexity is O(n2+m), n represents the number of points, m represents the number of edges
intg[N][N];// Store each edge
intdist[N];// Store the shortest distance from point 1 to each point
boolst[N];// Store whether the shortest path of each point has been determined
// Find the shortest path from point 1 to point n, if it does not exist, return -1
intdijkstra(){memset(dist,0x3f,sizeofdist);dist[1]=0;for(inti=0;i<n-1;i++){intt=-1;// Find the point with the smallest distance among the points whose shortest path has not been determined
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 points
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];}
3.2 Heap-Optimized Dijkstra
—— Template Problem AcWing 850. Dijkstra Shortest Path II
Time complexity O(mlogn), n represents the number of points, m represents the number of edges
typedefpair<int,int>PII;intn;// Number of points
inth[N],w[N],e[N],ne[N],idx;// Adjacency list stores all edges
intdist[N];// Store the distance from each point to point 1
boolst[N];// Store whether the shortest distance of each point has been determined
// Find the shortest path from point 1 to point n, if it does not exist, return -1
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 node number
while(heap.size()){autot=heap.top();heap.pop();intver=t.second,distance=t.first;if(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];}
3.3 Bellman-Ford Algorithm
—— Template Problem AcWing 853. Shortest Path with Edge Count Limit
Time complexity O(nm), n represents the number of points, m represents the number of edges
Note that in the template problem, you need to slightly modify the following template and add a backup array, see the template problem for details.
intn,m;// n represents the number of points, m represents the number of edges
intdist[N];// dist[x] stores the shortest path distance from 1 to x
structEdge// Edge, a represents the starting point, b represents the ending point, w represents the weight of the edge
{inta,b,w;}edges[M];// Find the shortest path distance from 1 to n, if it is impossible to go from 1 to n, return -1.
intbellman_ford(){memset(dist,0x3f,sizeofdist);dist[1]=0;// If the n-th iteration still relaxes the triangle inequality, it means that there is a shortest path of length n+1, and by the pigeonhole principle, there are at least two identical points in the path, indicating that there is a negative weight cycle in the graph.
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 Shortest Path
Average time complexity O(m), worst case O(nm), n represents the number of points, m represents the number of edges
intn;// Total number of points
inth[N],w[N],e[N],ne[N],idx;// Adjacency list stores all edges
intdist[N];// Store the shortest distance from each point to point 1
boolst[N];// Store whether each point is in the queue
// Find the shortest path distance from point 1 to point n, if it is impossible to go from point 1 to point n, return -1
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 already exists in the queue, do not insert j repeatedly
{q.push(j);st[j]=true;}}}}if(dist[n]==0x3f3f3f3f)return-1;returndist[n];}
3.5 SPFA to Determine if There is a Negative Cycle in the Graph
intn;// Total number of points
inth[N],w[N],e[N],ne[N],idx;// Adjacency list stores all edges
intdist[N],cnt[N];// dist[x] stores the shortest distance from point 1 to x, cnt[x] stores the number of points in the shortest path from 1 to x
boolst[N];// Store whether each point is in the queue
// If there is a negative cycle, return true, otherwise return false.
boolspfa(){// No need to initialize the dist array
// Principle: If there is a shortest path with n points (excluding itself), then there are n+1 points including itself, and by the pigeonhole principle, there are at least two identical points, so there is a cycle.
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;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 point 1 to x contains at least n points (excluding itself), it means there is 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 ends, 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 points
intg[N][N];// Adjacency matrix, store all edges
intdist[N];// Store the distance from other points to the current minimum spanning tree
boolst[N];// Store whether each point is already in the spanning tree
// If the graph is not connected, return INF (value is 0x3f3f3f3f), otherwise return the sum of the weights of the tree edges 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 points, m is the number of edges
intp[N];// Parent node array of union-find
structEdge// Store edges
{inta,b,w;booloperator<(constEdge&W)const{returnw<W.w;}}edges[M];intfind(intx)// Core operation of union-find
{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 union-find
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, merge the two connected components
{p[a]=b;res+=w;cnt++;}}if(cnt<n-1)returnINF;returnres;}
intn;// n represents the number of points
inth[N],e[M],ne[M],idx;// Adjacency list stores the graph
intcolor[N];// Represents the color of each point, -1 means uncolored, 0 means white, 1 means black
// Parameters: u represents the current node, c represents the color of the current point
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 represents the number of points in the first set, n2 represents the number of points in the second set
inth[N],e[M],ne[M],idx;// Adjacency list stores all edges, Hungarian algorithm only uses edges from the first set to the second set, so only one direction of edges is stored here
intmatch[N];// Store which point in the first set each point in the second set is currently matched with
boolst[N];// Indicates whether each point 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;}// Find the maximum matching number, enumerate whether each point in the first set can be matched with a point 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 whether x has been sieved
voidget_primes(intn){for(inti=2;i<=n;i++){if(st[i])continue;primes[cnt++]=i;for(intj=i+i;j<=n;j+=i)st[j]=true;}}
intprimes[N],cnt;// primes[] stores all prime numbers
boolst[N];// st[x] stores whether x has been sieved
voidget_primes(intn){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;}}}
intprimes[N],cnt;// primes[] stores all prime numbers
inteuler[N];// Store the Euler's function of each number
boolst[N];// st[x] stores whether x has been sieved
voidget_eulers(intn){euler[1]=1;for(inti=2;i<=n;i++){if(!st[i]){primes[cnt++]=i;euler[i]=i-1;}for(intj=0;primes[j]<=n/i;j++){intt=primes[j]*i;st[t]=true;if(i%primes[j]==0){euler[t]=euler[i]*primes[j];break;}euler[t]=euler[i]*(primes[j]-1);}}}
// 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 largest 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 largest absolute value to the top
for(inti=n;i>=c;i--)a[r][i]/=a[r][c];// Make the leading coefficient of the current row 1
for(inti=r+1;i<n;i++)// Use the current row to eliminate all columns below
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];return0;// Unique solution
}
// c[a][b] represents the number of ways to choose b 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 Calculate Combinations by Preprocessing Inverse Elements
—— Template Problem AcWing 886. Calculate Combinations II
First preprocess the remainder of all factorials fact[N], and the remainder of the inverse elements of all factorials infact[N]
If the modulus is a prime number, 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)// Fast exponentiation template
{intres=1;while(k){if(k&1)res=(LL)res*a%p;a=(LL)a*a%p;k>>=1;}returnres;}// Preprocess the remainder of factorials and the remainder of factorial inverse elements
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;}
Ifpisaprimenumber,foranyinteger1<=m<=n,thereis:C(n,m)=C(n%p,m%p)*C(n/p,m/p)(modp)intqmi(inta,intk,intp)// Fast exponentiation 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 the combination C(a, b) through 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 Factorization Method to Calculate Combinations
—— Template Problem AcWing 888. Calculate Combinations IV
When we need to find the real value of the combination number, rather than the remainder of a certain number, the factorization method is more useful:
1. Use the sieve method to find all prime numbers within the range
2. Use the formula C(a, b) = a! / b! / (a - b)! to find the number of times each prime factor appears. The number of times p appears in n! is n / p + n / p^2 + n / p^3 + …
3. Use high-precision multiplication to multiply all prime factors
intprimes[N],cnt;// Store all prime numbers
intsum[N];// Store the number of times each prime number appears
boolst[N];// Store whether each number has been sieved
voidget_primes(intn)// Linear sieve method to find prime numbers
{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)// Find the number of times 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 prime numbers within the range
for(inti=0;i<cnt;i++)// Find the number of times each prime factor appears
{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++)// Use high-precision multiplication to multiply all prime factors
for(intj=0;j<sum[i];j++)res=mul(res,primes[i]);
8 Game Theory
8.1 Catalan Numbers
—— Template Problem AcWing 889. 01 Sequence Satisfying Conditions
Given n 0s and n 1s, they are arranged in a sequence of length 2n in a certain order, satisfying that the number of 0s in any prefix is not less than the number of 1s. The number of such sequences is: Cat(n) = C(2n, n) / (n + 1)
NIM Game —— Template Problem AcWing 891. Nim Game
Given N piles of items, the i-th pile has Ai items. Two players take turns to act, each time they can choose any pile and take away any number of items, they can take away the entire pile, but they cannot take nothing. The player who takes the last item wins. Both players adopt the optimal strategy, ask whether the first player is sure to win.
We call this game a NIM game. The state faced during the game is called a situation. The first player to act in the whole game is called the first player, and the second player to act is called the second player. If in a certain situation, no matter what action is taken, the game will be lost, then the situation is called a losing situation.
The so-called optimal strategy means that if there is an action in a certain situation that makes the opponent face a losing situation after the action, then the action is preferred. At the same time, such a situation is called a winning situation. The game problems we discuss generally only consider the ideal situation, that is, both players make no mistakes and take optimal strategies when the game result.
The NIM game has no draw, only two situations: the first player is sure to win and the first player is sure to lose.
Theorem: The first player is sure to win in the NIM game if and only if A1 ^ A2 ^ … ^ An != 0
8.2 Fair Combinatorial Game ICG
If a game satisfies:
Two players take turns to act;
At any time during the game, the legal actions that can be performed are independent of which player it is;
The player who cannot act loses;
Then the game is called a fair combinatorial game.
The NIM game is a fair combinatorial game, but urban construction chess games, such as Go, are not fair combinatorial games. Because the two sides of the Go game can only place black and white pieces respectively, and the victory and defeat judgment is also more complicated, it does not satisfy condition 2 and condition 3.
8.3 Directed Graph Game
Given a directed acyclic graph, there is a unique starting point in the graph, and a chess piece is placed on the starting point. Two players take turns to move the chess piece along the directed edge, each time moving one step, and the player who cannot move loses. This game is called a directed graph game.
Any fair combinatorial game can be transformed into a directed graph game. The specific method is to regard each situation as a node in the graph, and connect directed edges from each situation to the next situation that can be reached by legal actions.
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 that does not belong to the set S, that is:
mex(S) = min{x}, x belongs to natural numbers, and x does not belong to S
8.5 SG Function
In a directed graph game, for each node x, let there be k directed edges from x, reaching nodes y1, y2, …, yk, define SG(x) as the result of the mex(S) operation on the set of SG function values of the successor nodes y1, y2, …, yk, that is:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
In particular, 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, that is, SG(G) = SG(s).
8.6 Sum of Directed Graph Games
—— Template Problem AcWing 893. Set-Nim Game
Let G1, G2, …, Gm be m directed graph games. Define a directed graph game G, whose action rule is to choose any directed graph game Gi and act one step on Gi. G is called the sum of directed graph games G1, G2, …, Gm.
The SG function value of the sum of directed graph games is equal to the XOR sum of the SG function values of each subgame it contains, that is:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
Theorem
A certain situation of a directed graph game is a winning situation if and only if the SG function value of the node corresponding to the situation is greater than 0.
A certain situation of a directed graph game is a losing situation if and only if the SG function value of the node corresponding to the situation is equal to 0.
Data Range
The time limit for general ACM or written test questions is 1 second or 2 seconds. In this case, the number of operations in C++ code is best controlled within $10^7$∼$10^8$. The following gives the time complexity of the code and the algorithm selection under different data ranges:
$n≤30$, Exponential level, dfs+pruning, state compression dp
$n≤100=>O(n^3)$, floyd, dp, Gaussian elimination
$n≤1000=>O(n^2)$, $O(n^{2}logn)$, dp, binary search, naive Dijkstra, naive Prim, Bellman-Ford
$n≤10000=>O(n ·\sqrt{n})$, Block linked list, block, Mo’s algorithm
$n≤100000=>O(nlogn)$=>Various sorts, segment tree, binary indexed tree, set/map, heap, topological sorting, dijkstra+heap, prim+heap, Kruskal, spfa, convex hull, half-plane intersection, binary search, CDQ divide and conquer, overall binary search, suffix array, tree chain decomposition, dynamic tree
$n≤1000000=>O(n)$, And O(nlogn) algorithms with smaller constants=>Monotonic queue, hash, double pointer scan, union-find, kmp, AC automaton, O(nlogn) methods with smaller constants: sort, binary indexed tree, heap, dijkstra, spfa
$n≤10000000=>O(n)$, Double pointer scan, kmp, AC automaton, linear sieve prime numbers
$n≤10^9=>O(\sqrt n)$, Determine prime numbers
$n≤10^{18}=>O(logn)$, Greatest common divisor, fast exponentiation, digit DP
$n≤10^{1000}=>O((logn)^2)$, High-precision addition, subtraction, multiplication, division
$n≤10^{100000}=>O(logk×loglogk)$, k represents the number of digits, FFT/NTT