搜索与图论

搜索与图论

1. DFS

DFS排列数字

给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

#include<iostream>
using namespace std;

const int N=10;

int path[N];
bool sign[N]; //看这个位置有没有搜索过

void dfs(int u,int n){
    if(u==n){ //如果u=n说明DFS已经遍历到了第n层,输出
        for(int i=0; i<n; i++) cout<<path[i]<<" ";
        cout<<endl;
        return;
    }
    
    for(int i=1;i<=n;i++){ //填1-n之间的数
        if(sign[i]==false){ //如果i没有被用过
            path[u]=i;
            sign[i]=true;
            dfs(u+1,n);   //递归到下一层。
            
            sign[i]=false;//递归完之后要回头搜索,而path里面的值会被覆盖,不需要恢复。
        }
    }
}

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

n-皇后问题

n−皇后问题是指将 n 个皇后放在 n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gbaTNhLP-1671943637107)(./assets/19_860e00c489-1_597ec77c49-8-queens.png)]

现在给定整数 n,请你输出所有的满足条件的棋子摆法。

输入格式

共一行,包含整数 n。

输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1 ≤ n ≤ 9 1≤n≤9 1n9

#include<iostream>
using namespace std;

const int N=10;

char g[N][N];
bool col[N],dg[2*N],udg[2*N]; //列,对角线和反对角线,对角线比列数多,直接开两倍

void dfs(int u,int n){
    if(u==n){//当放完最后一个皇后
        for(int i=0; i<n; i++) cout<<g[i]<<endl;
        cout<<endl;
        return;
    }
    
    for(int i=0;i<n;i++){
        // if(!col[i] && !dg[u+i]&& !udg[n-u+i]){
        if(col[i]==0 && dg[u+i]==false && udg[n-u+i]==false){
            //u+i 和 n-u+i 分别是第u行第i列两个对角线的标号。
            g[u][i]='Q';
            col[i]=dg[u+i]=udg[n-u+i]=true;
            dfs(u+1,n);
            col[i]=dg[u+i]=udg[n-u+i]=false;
            g[u][i]='.';
        }
    }
}

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

2. BFS

走迷宫

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。

数据保证 (1,1)处和 (n,m)处的数字为 0,且一定至少存在一条通路。

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤100

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

typedef pair<int,int> PII;

const int N=101;

int n,m;
int g[N][N];//存图
int d[N][N];//存当前点到起点的距离,初始化为-1表示没有访问过这个点
PII q[N*N];

PII Prev[N][N];

int bfs(){
    
    //初始化队列
    int hh=0,tt=0;
    q[tt++]={0,0};
    
    memset(d,-1,sizeof d);
    d[0][0]=0;
    
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; //分别表示向左,向下,向右,向上
    
    while(hh<tt){
        auto t=q[hh++];  //队头元素出队
        
        for(int i=0; i<4; i++){ //向4个方向扩展
            int x=t.first+dx[i],y=t.second+dy[i];
            if(x>=0 && x<n &&y>=0 && y<m && g[x][y]==0 && d[x][y] == -1){
                d[x][y]= d[t.first][t.second] + 1;
                
                Prev[x][y]=t; //(x,y)这个点的前一个点t
                
                q[tt++]= {x,y}; //新扩展出来的点入队
            }
        }
    }
    
    //输出路径
    // int x=n-1,y=m-1;
    // while(x || y){
    //     cout<<x<<" "<<y<<endl;
    //     auto t = Prev[x][y];
    //     x=t.first,y=t.second;
    // }
    
    
    return d[n-1][m-1];
}

int main(){
    cin>>n>>m;
    for(int i=0; i<n; i++)
        for(int j=0; j<m; j++)
            cin>>g[i][j];
            
    cout<<bfs()<<endl;
}

3. 树

树的存储

树与图的存储
树是一种特殊的图,与图的存储方式相同。
对于无向图中的边ab,存储两条有向边a->b, b->a。
因此我们可以只考虑有向图的存储。

(1) 邻接矩阵:g[a][b] 存储边a->b

(2) 邻接表:

// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;

// 添加一条边a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// 初始化
idx = 0;
memset(h, -1, sizeof h);

作者:yxc
链接:https://www.acwing.com/blog/content/405/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

树的遍历

树与图的遍历
时间复杂度 O(n+m), n 表示点数,m 表示边数
(1) 深度优先遍历 —— 模板题 AcWing 846. 树的重心

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

(2) 宽度优先遍历 —— 模板题 AcWing 847. 图中点的层次

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();
    
    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

树的重心

给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入格式

第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b之间存在一条边。

输出格式

输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1 ≤ n ≤ 105 1≤n≤105 1n105

#include<iostream>
#include<cstring>

using namespace std;
const int N=100001,M=N*2;
// M=N*2 因为两个节点间无向图要存两条边,而题目说有n-1条边
int n,m;
int h[N],e[M],ne[M],idx; 
bool visit[N];

int ans=N; //剩余各个连通块中点数的最大值,初始化为N

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


//以u为根的子树中点的数量
int dfs(int u){
    visit[u]= true; //标记为已访问
    
    int sum=1,res=0;
    
    for(int i=h[u]; i!=-1; i=ne[i]){
        int j=e[i];
        if(visit[j]==false){
            int s=dfs(j);
            res=max(res, s); //res是删除的这个点的所有子树中最多点的那个
            sum+=s;  //需要把所有子树的点的数量加起来算n-sum
        }
    }
    
    res=max(res ,n-sum); //删除的这个点的子树的点的数量res 和 剩下的n-sum
    
    ans= min(ans, res);  //在所有的结果中求最小值
    return sum;
}

int main(){
    cin>>n;
    memset(h, -1, sizeof h);
    
    for(int i=0; i<n-1; i++){
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }
    
    dfs(1);
    
    cout<<ans<<endl;
    return 0;
}

图中点的层次

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。

所有边的长度都是 1,点的编号为 1∼n。

请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1 的边。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

数据范围

1≤n,m≤105

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;

int n,m;
int h[N],e[N],ne[N],idx;
int d[N],q[N];

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

int bfs(){
    int hh=0,tt=0;
    q[0]=1;
    
    memset(d, -1, sizeof d);
    
    d[1]=0;
    
    while(hh<=tt){
        int t=q[hh++];
        
        for(int i=h[t]; i!= -1; i=ne[i]){
            int j=e[i];
            if(d[j]== -1){
                d[j]=d[t]+1;
                q[++tt]=j;
            }
        }
    }
    
    return d[n];
}

int main(){
    cin>>n>>m;
    
    memset(h, -1, sizeof h);
    
    for(int i=0; i<m; i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    
    cout<<bfs();
    return 0;
}

4. 有向图的拓扑排序

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。

请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。

若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1。

数据范围

1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105

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

using namespace std;

const int N=100001;

int h[N],e[N],ne[N],idx;

int q[N],d[N]; //q为存拓扑序的队列,d为每个节点的入度

void add(int a, int b){ //添加一条a到b 的边
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx;
    idx++;
}

bool topsort(int n){  //返回值代表是否有拓扑序
    int hh=0, tt=-1;
    
    for(int i=1; i<= n; i++){
        if(!d[i]) q[++tt]=i; //如果入度为0,就让它入队
    }
    
    while(hh <= tt)
    {
        int t = q[hh++];
        
        //取出队头,也就是最先进队的,把它后面连的点的入度减一,然后看是否为0
        //为0 则入队
        for(int i=h[t]; i!= -1; i=ne[i]){
            int j=e[i];
            d[j]--;
            if(d[j]==0) q[++tt]=j;
        }
    }
    return tt==n-1;
}

int main(){
    int n,m;
    cin>>n>>m;
    
    memset(h, -1, sizeof h);
    
    for(int i=0; i<m; i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
        d[b]++;
    }
    
    if(topsort(n)){
        for(int i=0; i<n; i++) cout<<q[i]<<" ";
        cout<<endl;
    }
    else cout<<-1;
    return 0;
}


5.dijkstra算法

朴素Dijkstra求最短路 I

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n≤500,
1≤m≤10^5,
图中涉及边长均不超过10000。

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

const int N=501;

int n,m;
int g[N][N]; //图的邻接矩阵
int dist[N]; //当前点到起点的距离
bool st[N];

int dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0; //起点
    
    for(int i=0; i<n; i++){ //迭代n次,就可以求出最短路
        
        int t=-1; //
        
        for(int j=1; j<=n; j++){
            if(!st[j] && (t==-1 || dist[t]>dist[j])) 
                t=j;
        }
        st[t]=true; //把t加入S集中
        
        for(int j=1; j<=n; j++){ //看加入t之后能不能代替某些路径
            dist[j]=min(dist[j], dist[t]+g[t][j]);
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
    
}

int main(){
    cin>>n>>m;
    
    memset(g, 0x3f, sizeof g);
    
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c); //有重边,取较短的那一条 
    }
    
    int t=dijkstra();
    
    cout<<t<<endl;
    return 0;
}

堆优化dijkstra

//堆优化dijkstra
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>

using namespace std;

typedef pair<int, int> PII;


const int N=150010;

int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储

int dist[N]; //当前点到起点的距离
bool st[N];

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


int dijkstra(){
    
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0; //起点
    
    priority_queue<PII,vector<PII>, greater<PII>> heap;
    heap.push({0, 1}); //起点进堆
    
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop(); //取出堆头元素
        
        int ver= t.second, distance=t.first;
        if(st[ver]) continue; //如果这个点在S集里面,略过
        
        st[ver]=true; 
        
        for(int i=h[ver]; i!=-1; i=ne[i]){
            int j=e[i];
            if(dist[j]>distance + w[i]){
                dist[j]=distance + w[i];
                heap.push({dist[j],j});
            }
        }
    }
    
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
    
}

int main(){
    scanf("%d%d",&n,&m);
    
    memset(h, -1, sizeof h);
    
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c); //使用邻接表存储图
    }
    
    int t=dijkstra();
    
    cout<<t<<endl;
    return 0;
}

6. Bellman_ford算法

有边数限制的最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出从 1 号点到 n 号点的最多经过 kk 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible

注意:图中可能 存在负权回路

输入格式

第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

点的编号为 1∼n。

输出格式

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

数据范围

1≤n,k≤500,
1≤m≤10000,
1≤x,y≤n,
任意边长的绝对值不超过 10000。

#include<iostream>
#include<cstring>

using namespace std;

const int N=510,M=10001;

int n,m,k;
int dist[N],backup[N];

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++){  //不超过k条边,使用k次迭代
        memcpy(backup, dist, sizeof dist);  //每次用前一次的结果,防止串联导致不满足不超过k条边
        for(int j=0; j<m; j++){
            int a=edges[j].a,b=edges[j].b,w=edges[j].w;
            dist[b]=min(dist[b], backup[a]+w);
        }
    }
    
    //if(dist[n]>0x3f3f3f3f/2) return -1; 
    return dist[n];
}

int main(){
    cin>>n>>m>>k;
    
    for(int i=0; i<m; i++){
        int a,b,w;
        cin>>a>>b>>w;
        edges[i]={a,b,w};
    }
    int t=bellman_ford();
    
    if(t>0x3f3f3f3f/2) cout<<"impossible"<<endl;
    //除以2是因为有负权边,正无穷的值可能被迭代不等于0x3f
    
    else cout<<t<<endl;
    
    return 0;
}

7.spfa算法

spfa求最短路,

比dijkstra快,但某些情况会被卡

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible

数据保证不存在负权回路。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible

数据范围

1≤n,m≤1051≤n,m≤105,
图中涉及边长绝对值均不超过 1000010000。

//SPFA,不被卡的话比dijkstra快,阴险的出题人会卡SPFA
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>

using namespace std;

typedef pair<int, int> PII;


const int N=150010;

int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储

int dist[N]; //当前点到起点的距离
bool st[N];

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


int spfa(){
    
    memset(dist, 0x3f, sizeof dist);
    dist[1]=0;
    
    queue<int> q;
    q.push(1);
    
    st[1]=true;
    
    while(q.size()>0){
        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(st[j]==false){
                    q.push(j);  //让更新后变小了之后的点入队,如果没变小就不管
                    st[j]=true;
                }
            }
        }
    }
    return dist[n];
}

int main(){
    scanf("%d%d",&n,&m);
    
    memset(h, -1, sizeof h);
    
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c); //使用邻接表存储图
    }
    
    int t=spfa();
    if(t== 0x3f3f3f3f) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    return 0;
}

spfa判断负环

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路。

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No

数据范围

1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。

//SPFA,不被卡的话比dijkstra快,阴险的出题人会卡SPFA
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
#include<vector>

using namespace std;

typedef pair<int, int> PII;


const int N=150010;

int n,m;
int h[N],w[N],e[N],ne[N],idx; //邻接表存储

int dist[N],cnt[N]; //当前点到起点的距离
bool st[N];


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


int spfa(){
    
    //不需要初始化,因为求得不是距离,是负环
    // memset(dist, 0x3f, sizeof dist);
    // dist[1]=0;
    

    queue<int> q;
    for(int i=1; i<=n; i++){
        st[i]=true;
        q.push(i);
    }
    //因为如果从1开始的话,可能到不了负环,
    //因此要把所有的点都加入队列
    // q.push(1);
    // st[1]=true;
    
    while(q.size()>0){
        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;
                
                if(cnt[j] >=n) return true;
                
                if(st[j]==false){
                    q.push(j);  //让更新后变小了之后的点入队,如果没变小就不管
                    st[j]=true;
                }
            }
        }
    }
    return false;
}

int main(){
    scanf("%d%d",&n,&m);
    
    memset(h, -1, sizeof h);
    
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c); //使用邻接表存储图
    }
    
    if(spfa()) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;

    return 0;
}

8. floyd算法

Floyd求最短路

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。

再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 impossible

数据保证图中不存在负权回路。

输入格式

第一行包含三个整数 n,m,k。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。

输出格式

共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible

数据范围

1≤n≤200,
1≤k≤n2
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。

#include<iostream>
#include<cstring>

using namespace std;

const int N=210, INF=1e9;

int n,m,q;
int d[N][N];

void floyd(){
    for(int k=1;k<=n;k++){
        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>>q;
    
    for(int i=1;i<=n; i++){
        for(int j=1;j<=n; j++){
            if(i==j) d[i][j]=0;
            else d[i][j]=INF;
        }
    }
    
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        d[a][b]=min(d[a][b], w); //可能有重边
    }
    
    floyd();
    
    while(q--){
        int a,b;
        cin>>a>>b;
        
        if(d[a][b]>INF/2) cout<<"impossible"<<endl;
        
        else cout<<d[a][b]<<endl;
    }
    
    return 0;
}

9. prim算法(求最小生成树)

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,EE 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中 n−1条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1≤n≤500,
1≤m≤10^5,
图中涉及边的边权的绝对值均不超过 10000。

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

const int N=501,INF=0x3f3f3f3f;

int n,m; //点数和边数
int g[N][N]; //稠密图用邻接矩阵
int dist[N];
bool st[N]; //更新过点之后置为true,默认为FALSE

int prim(){
    memset(dist, 0x3f, sizeof dist);
    
    int res=0;
    
    for(int i=0; i<n; i++){ //迭代n次
        int t=-1;
        for(int j=1; j<=n; j++){
            if(st[j]==false && (t==-1 || dist[t] > dist[j]))
                t=j;
        }
        
        if(i && dist[t]==INF) return INF; //如果不是第一次更新且dist[t]为正无穷则不存在
        
        for(int j=1; j<=n; j++) dist[j]=min(dist[j], g[t][j]);
        
        if(i) res+=dist[t];//会出现自环的问题,在main函数里面处理了一下
        st[t]=true;
    }
    return res;
}


int main(){
    cin>>n>>m;
    
    memset(g, 0x3f, sizeof g);
    
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b], c); //有重边取较短边
    }
    
    for(int i=1; i<=n; i++){
        g[i][i]=INF; //去掉自环
    }
    
    
    int t=prim();
    if(t==INF) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    
    return 0;
}

10. Kruskal算法(求最小生成树)

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w表示点 u和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105,
1 ≤ m ≤ 2 ∗ 1 0 5 1≤m≤2*10^5 1m2105,
图中涉及边的边权的绝对值均不超过 1000。

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

const int N=200001;
int n,m;
int p[N];

struct Edge{
    int a,b,w;
    
    bool operator< (const Edge &W)const
    {
        return w<W.w;
    }
}edges[N];

//并查集
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}

int main(){
    cin>>n>>m;
    
    for(int i=0; i<m; i++){
        int a,b,w;
        cin>>a>>b>>w;
        edges[i]={a,b,w};
    }
    
    sort(edges,edges+m);
    
    for(int i=1; i<=n; i++) p[i]=i; //初始化
    
    int res=0,cnt=0;
    
    for(int i=0; i<m; i++){
        int a=edges[i].a, b=edges[i].b, w=edges[i].w;
        
        a=find(a),b=find(b);
        
        if(a!=b){//a和b不连通
            p[a]=b;// 让a和b联通
            res+=w;
            cnt++;
        }
    }
    
    if(cnt < n-1) cout<<"impossible";
    else cout<<res;
    
    return 0;
}

11. 二分图

染色法判定二分图

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。

请你判断这个图是否是二分图。

输入格式

第一行包含两个整数 n 和 m。

接下来 mm 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。

输出格式

如果给定图是二分图,则输出 Yes,否则输出 No

数据范围

1≤n,m≤105

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

const int N=100001,M=2*N;
int h[N],e[M],ne[M],idx;
int color[N];
int n,m;

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

bool dfs(int u,int c){ 
    color[u]=c; //把u这个点染成c
    
    for(int i=h[u]; i!=-1; i=ne[i]){
        int j=e[i];
        if(!color[j]){
            //把j这个点染成3-c(1或2)
            if(!dfs(j,3-c)) return false; //如果返回值为false,说明有矛盾
        }
        else if(color[j]==c) return false; //如果j已经染色并且和u相同,那么就有矛盾
    }
    
    return true; //没有矛盾
}

int main(){
    cin>>n>>m;
    
    memset(h, -1, sizeof h); //初始化h[N]
    
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b), add(b,a); //无向图
    }
    
    bool flag=true; //染色过程是否出现矛盾
    
    for(int i=1; i<=n; i++){
        if(!color[i]){ //如果i没染色
            if(!dfs(i,1)){
                flag=false;
                break;
            }
        }
    }
    
    if(flag) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    
    return 0;
}

匈牙利算法解决二分图最大匹配

给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2个点(编号 1∼n2),二分图共包含 m 条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式

第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

输出格式

输出一个整数,表示二分图的最大匹配数。

数据范围

1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105

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

const int N=501, M=100001;

int n1,n2,m; //分别表示左半边、右半边点的数量,以及之间的边数
 
int h[N],e[M],ne[M],idx; //邻接表存储

int match[N]; //存匹配的边的情况

bool st[N];

//图的邻接表存储
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx;
    idx++;
}

//匈牙利算法——二分图匹配
bool find(int x){
    for(int i=h[x]; i!=-1; i=ne[i]){
        int j=e[i];
        if(!st[j]){ //如果女生j还没有喜欢其它男生
            st[j]=true;
            if(match[j]==0 || find(match[j])){
                //如果女生j没和其它男生配对,或者x可以找到除j外的第二个
                match[j]=x; //就让x跟j匹配
                return true;
            }
        }
    }
    return false;
}

int main(){
    cin>>n1>>n2>>m;
    
    memset(h, -1, sizeof h);
    
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b); //在a、b之间加一条边,表示男生a正在追女生b
    }
    
    int res=0;
    for(int i=1; i<=n1; i++){
        memset(st, false, sizeof st);
        if(find(i)) res++;
    }
    
    cout<<res;
    
    return 0;
}

n2个点(编号 1∼n2),二分图共包含 m 条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

输入格式

第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

输出格式

输出一个整数,表示二分图的最大匹配数。

数据范围

1≤n1,n2≤500,
1≤u≤n1,
1≤v≤n2,
1≤m≤105

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

const int N=501, M=100001;

int n1,n2,m; //分别表示左半边、右半边点的数量,以及之间的边数
 
int h[N],e[M],ne[M],idx; //邻接表存储

int match[N]; //存匹配的边的情况

bool st[N];

//图的邻接表存储
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx;
    idx++;
}

//匈牙利算法——二分图匹配
bool find(int x){
    for(int i=h[x]; i!=-1; i=ne[i]){
        int j=e[i];
        if(!st[j]){ //如果女生j还没有喜欢其它男生
            st[j]=true;
            if(match[j]==0 || find(match[j])){
                //如果女生j没和其它男生配对,或者x可以找到除j外的第二个
                match[j]=x; //就让x跟j匹配
                return true;
            }
        }
    }
    return false;
}

int main(){
    cin>>n1>>n2>>m;
    
    memset(h, -1, sizeof h);
    
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b); //在a、b之间加一条边,表示男生a正在追女生b
    }
    
    int res=0;
    for(int i=1; i<=n1; i++){
        memset(st, false, sizeof st);
        if(find(i)) res++;
    }
    
    cout<<res;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值