ACwing Chapter 3 search and graph theory

Keywords: C++ Algorithm data structure

1, DFS


There are two important concepts in DFS: backtracking and pruning
When the weight of all edges in the graph is 1, the BFS must find the shortest path

When tracing back, we must pay attention to restoring the scene
Arrange numbers

#include<iostream>
using namespace std;
const int N = 10;

int n;
int path[N];//Record all search paths
bool st[N];//Record whether these points have been used. 1 means yes and 0 means No

void dfs(int u) // Layer u
{
    if(u == n)//Starting from 0 as the first layer, when the last layer is searched, the path is output and the recursion ends
    {
        for(int i=0;i<n;i++)
        {
            printf("%d ",path[i]);
        }
        puts("");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(!st[i])
        {
            path[u] = i;//Write path record
            st[i] = true;//Update status is used
            dfs(u+1);//Find the number for the next floor
            //----------------------------------The next level of recursion ends, and it's time to restore the state
            st[i] = false;//Update status is not used
            path[u] = 0;//Clear the layer path record
        }
    }
}

int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

n-queen problem

Pruning: if you judge in advance that the current scheme must be illegal, there is no need to search down and go back directly

There are 16 positive diagonals and 16 reverse diagonals in the figure
Positive diagonal: a straight line with a slope of - 1 and passing through the point
Inverse diagonal: a straight line with a slope of 1 and passing through the point

#include<iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N];//Recording scheme
bool col[N],dj[2*N],fdj[2*N];//Same row (column), positive diagonal, reverse diagonal

void dfs(int u)
{
    if(u == n)//When the last row and leaf node are searched, the scheme is output
    {
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                printf("%c",g[i][j]);
            }
            puts("");
        }
        puts("");
        return;
    }
    for(int i = 0;i < n;i++)//Enumerate i:x u:y from the first column x
    {
        if(!col[i] && !dj[i+u] && !fdj[n + u - i])//Prevent the opposition angle from being negative and add n to the normal range
        {
            g[u][i] = 'Q';//Place queen
            col[i] = dj[i+u] = fdj[u-i+n] = true;//Status update
            dfs(u+1);//Search next column
            //Restore state to prepare for backtracking
            g[u][i] = '.';
            col[i] = dj[i+u] = fdj[u-i+n] = false;
        }
    }
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            g[i][j] = '.';//initialization
        }
    }
    dfs(0);
    return 0;
}

2, BFS

Labyrinth

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 105;
typedef pair<int,int> PII;
queue<PII>q;
int n,m;
int g[M][M];
int cnt[M][M];//Stores the number of steps taken to each point
int dx[4] = {0,0,-1,1};//x direction
int dy[4] = {1,-1,0,0};//y direction: up, down, left and right
int bfs()
{
    q.push({0,0});
    cnt[0][0] = 0;
    while(!q.empty())
    {
        PII pos = q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int y = pos.first + dy[i];
            int x = pos.second + dx[i];
            if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //Within the range, not a wall, and did not walk through
            {
                q.push({y,x});
                cnt[y][x] = cnt[pos.first][pos.second] + 1; //The number of steps at this point is equal to the number of steps at the previous point plus 1
            }
        }
    }
    return cnt[n-1][m-1];//Returns the number of steps for the exit point
}

int main()
{
    cin>>n>>m;//For graphs with all edge weights of 1, bfs can find the shortest path, but it can't go back (go twice at a point)
    memset(cnt,-1,sizeof cnt);//Initialize the cnt array to - 1, indicating that the point has not been passed
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            cin>>g[i][j];
        }
    }
    cout<<bfs()<<endl;
    return 0;
}

Output path

 if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //Within the range, not a wall, and did not walk through
{
q.push({y,x});
cnt[y][x] = cnt[pos.first][pos.second] + 1; //The number of steps at this point is equal to the number of steps at the previous point plus 1
Prev[y][x] = pos;
}
PII Prev[M][M];
while(i || j)
{
        cout<<i<<" "<<j<<endl;
        pos = Prev[i][j];
        i = pos.first;
        j = pos.second;
}

3, Storage of trees and graphs


Edge multipurpose matrix, point multipurpose table
The adjacency table usually selects the head insertion method when inserting

Array analog linked list


e [] is equivalent to val and ne [] is equivalent to next

4, Priority traversal of trees and graphs

Center of gravity of tree

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1e5+10;

int h[N],e[2*N],ne[2*N],idx;
bool st[N];
int n;
int ans = N;//Answer: the minimum value of the largest subtree
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dfs(int u)
{
    st[u] = true;
    
    int sum = 1;//Size of current subtree u
    int res = 0;//The maximum number of points in each connected block after deleting the point
    for(int i = h[u];i!=-1;i = ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            int s = dfs(j);//Current size of this sub tree j
            res = max(res,s);
            sum += s;//The size of the subtree u is equal to the sum of the subtrees that make up it
        }
    }
    res = max(res,n-sum);//Compare with the subtree where the parent node is located
    ans = min(ans,res);//Compare with the answer and update the answer
    return sum;//Returns the size of the subtree
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);//The edges are bidirectional
    }
    dfs(1);
    cout<<ans<<endl;
}

Hierarchy of points in BFS diagram
Classic BFS

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int M = 1e5+10;
int h[M],e[M],ne[M],idx;
int n,m;
int d[M];//Record the distance from the point to the starting point
queue<int>q;
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int bfs()
{
    q.push(1);
    memset(d,-1,sizeof d);
    d[1] = 0;//The starting distance is 0
    while(!q.empty())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t];i != -1;i = ne[i])
        {
            int j = e[i];
            if(d[j] == -1)//If the distance is - 1, it indicates that it has not been traversed before
            {
                q.push(j);
                d[j] = d[t] + 1;//Update the distance, and the distance between adjacent points will be increased by 1
            }
        }
    }
    return d[n];
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);//Note: the initialization chain header must be - 1 here, otherwise TLE
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);//Unidirectional edges: a to b
    }
    cout<<bfs()<<endl;
    return 0;
}

5, Topological sequence

Directed acyclic graph must have topological sequence, which is also called topological graph
Penetration: how many edges point to this node
Out degree: the node has several points (several edges)

If there is a ring, all points in the ring will not join the team because no breakthrough can be found
848. Topological sequence of digraph
Note: the answer to this question is not unique. Just output the legal topological order

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M = 1e5 + 10;
int e[M],ne[M],h[M],idx;
int d[M],q[M],hh=0,tt=-1;//d [] is the penetration of each point. Note: the array simulates the queue, and hh is 0
int n,m,x,y;

void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    idx ++;
    d[b]++;
}

bool TopSort()//bfs thinking
{
    for(int i=1;i<=n;i++)//First queue all points with degree 0 in the graph
    {
        if(!d[i]) q[++tt] = i;
    }
    while(tt >= hh)//Queue is not empty
    {
        int t = q[hh++];//Out of the team
        for(int i=h[t];i!=-1;i = ne[i])//Traverse all points pointed to by this point
        {
            int tmp = e[i];
            d[tmp]--;//Delete an edge pointing to the point and reduce the entry by one
            if(!d[tmp]) q[++tt] = tmp;//If the entry degree is 0, it indicates that it is the topological order's turn to join the team
        }
    }
    if(tt == n-1) return true;//If all points join the team, it means that it is an acyclic graph
    else return false;
}

int main()
{
    memset(h,-1,sizeof h);//Be sure to initialize that each header node is empty
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y;
        add(x,y);
    }
    if(TopSort())
    {
        for(int i=0;i<n;i++)//When leaving the queue, only the head pointer moves, the actual array elements remain unchanged, and the queue order is the topological order
        {
            cout<<q[i]<<" ";
        }
        puts("");
    }
    else puts("-1");
    return 0;
}

6, Shortest path algorithm

Source point: start point
Meeting point: end point
Single source shortest path: there is only one starting point
Multi source sink shortest path: multiple starting points

1. Simple dijkstra algorithm

Step 1:
Initialization distance dist[1] = 0,dist[i] = positive infinity
Step 2:
for i :[1,n)
T < - find the nearest point that is not in the set S
S < - t add t to s
Update the distance of other points with t

Dijkstra finding the shortest path I

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505;
const int INF = 0x3f3f3f3f;//Maximum int value, infinity
int g[N][N]; //Adjacency matrix storage dense graph
int dist[N]; //Distance from point 1 to point n
int n,m,x,y,z;
bool st[N];//Is the shortest distance determined
int Dijkstra()
{
    dist[1] = 0;//The starting distance is 0
    for(int i=1;i<=n;i++)//n points, n cycles
    {
        int t = -1;//Stores the point with the smallest distance that is not currently determined
        for(int j = 1;j <= n;j++)//o(n) find the point with the smallest distance
        {
            if(!st[j] && (t==-1 || dist[j] < dist[t]))
            {
                t = j;
            }
        }
        
        st[t] = true;//Mark the shortest distance determined
        
        for(int j=1;j<=n;j++)//Use this point to update the shortest distance of all points
        {
            dist[j] = min(dist[j],dist[t] + g[t][j]);
        }
    }
    if(dist[n] == INF) return -1;//Infinity means there is no path
    else return dist[n];
}

int main()
{
    cin>>n>>m;
    memset(g,INF,sizeof g);//Initialize all paths in the graph to infinity
    memset(dist,INF,sizeof dist);//Initialize all distances to infinity
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        g[x][y] = min(g[x][y],z);//There are multiple edges and self rings in the graph, which can be solved by taking the shortest path
    }
    cout<<Dijkstra()<<endl;
}

Infinite constant commonly used in ACM -- 0x3f3f3f3f

2. dijkstra algorithm of heap optimized version

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int M = 1.5*1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int,int>PII;//first represents the distance from the starting point, and second represents the number of points

int h[M],e[M],ne[M],w[M],idx;//Sparse graphs are stored using adjacency tables, w[] representing the weights of edges
int n,m,x,y,z;
int dist[M];
bool st[M];

void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;//Maintain edge weights
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int Dijkstra()//Heap optimized Dijkstra algorithm, a bit like bfs
{
    priority_queue<PII,vector<PII>,greater<PII>>heap;//Define a small root heap
    heap.push({0,1});//First, the distance from point 1 is 0 and put into the heap
    dist[1] = 0;//ditto
    while(!heap.empty())
    {
        PII t = heap.top();
        heap.pop();
        
        int num = t.second,distance = t.first;
        if(st[num]) continue;//If the shortest distance has been determined, skip
        st[num] = true;//Note: the order in which the status of the shortest distance has been determined is maintained
        for(int i = h[num];i!=-1;i=ne[i])//Maintain the shortest distance of adjacent points with the point with the shortest distance determined
        {
            int num1 = e[i];
            if(dist[num1] > distance + w[i])
            {
                dist[num1] = distance + w[i];
                heap.push({dist[num1],num1});
            }
        }
    }
    if(dist[n] == INF) return -1;//No access
    else return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    memset(dist,INF,sizeof dist);//The initialization linked list is empty, and the shortest distance is infinity
    
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    cout<<Dijkstra()<<endl;
    return 0;
}

3. Bellman Ford algorithm (dealing with negative edge weight)


There may be no negative weight loop in the graph with solution, and the algorithm can find the negative weight loop
Shortest path with edge limit

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 505,M = 10005,INF = 0x3f3f3f3f;
int n,m,k,x,y,z;
int dist[N],backup[N];//backup is used to record the last dist to prevent updating the distance of multiple points in one i cycle
struct edge
{
    int x;
    int y;
    int z;
}Edge[M];

int Bellman_Ford()
{
    memset(dist,INF,sizeof dist);//First, update the dist of each point to infinity
    dist[1] = 0;//Source distance is 0
    for(int i=0;i<k;i++)//The title requires a maximum of k edges
    {
        memcpy(backup,dist,sizeof dist);//Copy dist after the last i cycle to backup
        for(int j=0;j<m;j++)
        {
            int a = Edge[j].x;
            int b = Edge[j].y;
            int w = Edge[j].z;
            dist[b] = min(dist[b],backup[a] + w);
        }
    }
    if(dist[n] >= INF/2) return -2;//Because there is a negative edge, when the meaning of the question is not satisfied, the end distance is not necessarily infinity
    //But it must be a large number
    else return dist[n];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        Edge[i] = {x,y,z};
    }
    int t = Bellman_Ford();
    if(t == -2) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

4.SPFA algorithm (used more)

spfa finding the shortest path

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;

int h[M],e[M],w[M],ne[M],idx;
int dist[M];
bool st[M];//Indicates whether the point is in the queue. 1 is yes and 0 is No
int n,m,x,y,z;
void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int spfa()//The idea is similar to dijkstra algorithm, but the difference lies in the meaning of st
{
    memset(dist,INF,sizeof dist);
    dist[1] = 0;
    queue<int>q;
    q.push(1);
    st[1] = true;
    while(!q.empty())
    {
        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];//In case of distance update
                if(!st[j])//And the point is not in the queue
                {
                    q.push(j);//Join the team and update st[j] the status
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == INF) return -2;//Returning to - 1 will wa drop, and there may be a negative edge
    else return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    int t = spfa();
    if(t==-2) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

spfa judging negative ring

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;

int h[M],e[M],w[M],ne[M],idx;
int dist[M],cnt[M];//Record the number of edges in the path from point 1 to point n
bool st[M];//Indicates whether the point is in the queue. 1 is yes and 0 is No
int n,m,x,y,z;
void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

bool spfa()//spfa algorithm to find whether there is a negative ring, the idea comes from the drawer method
{
    memset(dist,INF,sizeof dist);
    //dist[1] = 0;
    queue<int>q;
    for(int i=1;i<=n;i++)//Because you may not be able to get to point n from point 1, you must join the team at each point at first
    {
        q.push(i);
        st[i] = true;
    }
    while(!q.empty())
    {
        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];//If a distance update occurs and the point is not in the queue
                cnt[j] = cnt[t] + 1;//The number of adjacent points differs by 1
                if(cnt[j] >= n) return true;//If the number of edges on the path is greater than or equal to the number of all points, there must be a negative ring
                if(!st[j])
                {
                    q.push(j);//Join the team and update st[j] the status
                    st[j] = true;
                }
            }
        }
    }
    return false;//No negative ring
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}

Floyd algorithm (O^3)


Floyd finds the shortest path

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 205,INF = 0x3f3f3f3f;
int n,m,x,y,z,k;
int d[N][N];
void Floyd() //dynamic programming
{
    for(int k=1;k<=n;k++) //k must be in the outer loop, and the order of I and j loops is variable
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                d[i][j] = min(d[i][j],d[i][k] + d[k][j]);
            }
        }
    }
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)//Initialize distance array
    {
        for(int j=1;j<=n;j++)
        {
            if(i == j) d[i][j] = 0;//The same point distance is 0 
            else d[i][j] = INF;//Otherwise set to infinity
        }
    }
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        d[x][y] = min(d[x][y],z);//If there are multiple edges, the edge with the smallest distance shall prevail
    }
    Floyd();
    while(k--)
    {
        cin>>x>>y;
        if(d[x][y] >= INF/2) puts("impossible");//There is a negative edge, so the path distance is not necessarily infinite
        else cout<<d[x][y]<<endl;
    }
    return 0;
}

7, Minimum spanning tree


Naive Prim algorithm (O^2)


858. Prim algorithm for minimum spanning tree

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];//Represents the distance from a point to a point in the collection
bool st[N];

int prim()
{
    int res = 0;
    memset(dist,INF,sizeof dist);
    for(int i=0;i<n;i++)
    {
        int t = -1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j] && (t == -1 || dist[j] < dist[t])) //J is outside the set, and no point is found or j distance is small
            {
                t = j;
            }
        }
        if(i && dist[t] == INF) return INF; //Not the first point and the distance from the nearest point is infinite,
        //Illustration diagram is not connected
        if(i) res += dist[t];//As long as it's not the first side, add the distance of t to the answer
        
        for(int j=1;j<=n;j++)
        {
            dist[j] = min(dist[j],g[t][j]);
        }
        st[t] = true; //Indicates that the current point is added to the collection
    }
    return res;
}

int main()
{
    cin>>n>>m;
    memset(g,INF,sizeof g);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b] = g[b][a] = min(c,g[a][b]);//Undirected graph
    }
    int t = prim();
    if(t == INF) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

Kruskal algorithm (fast sorting + joint search set)


859. Kruskal algorithm for minimum spanning tree

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 2e5+20,INF = 0x3f3f3f3f;
int p[M];
int n,m,a,b,c;
struct Edge
{
    int a;
    int b;
    int w;
}edges[M];

int find(int a) //Find the ancestor of the collection of point a
{
    if(a!=p[a]) p[a] = find(p[a]);
    return p[a];
}
bool cmp(struct Edge a,struct Edge b)
{
    return a.w < b.w;
}

void kruskal()
{
    int cnt = 0,res = 0;
    sort(edges+1,edges+m+1,cmp);//Sort each edge in ascending order
    for(int i=1;i<=n;i++) //Initialize all collections
    {
        p[i] = i;
    }
    for(int i=1;i<=m;i++)
    {
        int a = edges[i].a;
        int b = edges[i].b;
        int w = edges[i].w;
        a = find(a),b = find(b); //If not in a set, merge
        if(a!=b)
        {
            p[b] = a;
            cnt++;
            res += w;
        }
    }
    
    if(cnt < n-1) puts("impossible");//The merging times are less than n-1, indicating that the graph is not connected
    else cout<<res<<endl;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edges[i] = {a,b,c};
    }
    
    kruskal();

    return 0;
}

Determination of bipartite graph by coloring method

Bipartite graph, also known as bipartite graph, is a special model in graph theory. Let G=(V,E) be an undirected graph. If vertex v can be divided into two disjoint subsets (A,B), and the two vertices i and j associated with each edge (i, J) in the graph belong to these two different vertex sets (i in A,j in B), then graph G is called a bipartite graph.
Bipartite graph if and only if the graph does not contain odd rings
Determination of bipartite graph by coloring method

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10; // Undirected graph, so the maximum number of edges is 2 times
int e[M], ne[M], h[N], idx;
int st[N];

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool dfs(int u, int color) {
    st[u] = color;

    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if(!st[j]) {
            if(!dfs(j, 3 - color)) return false;
        }else if(st[j] == color) return false;
    }

    return true;
}

int main(){
    int n, m;
    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);  // Undirected graph
    }

    bool flag = true;
    for(int i = 1; i <= n; i ++){
        if(!st[i]){
            if(!dfs(i, 1)){
                flag = false;
                break;
            }
        }
    }

    if(flag) puts("Yes");
    else puts("No");
    return 0;
}

Posted by _OwNeD.YoU_ on Thu, 18 Nov 2021 05:30:57 -0800