Acwing 算法基础课 第三讲

DFS

用栈的方式来记录深度优先遍历的路径(或者用递归)
需 配合 剪枝 来优化时间复杂度。

BFS

用队列的方式来实现。

树和图的深度优先遍历

表示一个图的方法

当图比较稠密的时候(边数 = 2倍 节点数),适合用邻接矩阵存
否则 适合用邻接链表存

邻接链表法:

一个图有n个点,m条边
则创建n条链表,每个链表表示一个节点的邻接点是谁
而图的数组来存这些链表头的地址。

邻接矩阵法:

一个图有n个点,m条边
则用一个n*n的矩阵来表示边,如果 i 行 j 列值为1,则节点 i 和 节点 j 之间存在一条边

树和图的宽度优先遍历

拓扑排序问题

在这里插入图片描述
拓扑排序问题,指的是,给出一个有向图,对图中的点进行排序,使 不存在 反向的边 即序列后面的点有到前面点 的边。

这也用到了队列。
首先,对原始的图,找到入度为0的点,这种入度为0的点就可以放入拓扑序中了,将这些点放入一个队列中。
然后依次遍历序列中的点,当遍历到一个点的时候,就把 它 出去的边都删掉,这个操作会使某些点的入度减一,就可能产生入度为0的点,如果有,就加入队列,依次循环,直到队列为空,如果最后队列空,且拓扑序的长度等于节点数就存在拓扑序,否则,不存在。

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

const int N = 1e5+10;
int head[N], nxt[N], val[N], ttlen;
int inDeg[N];

int n, m; 

void add(int u, int v){
    val[ttlen] = v;
    nxt[ttlen] = head[u];
    head[u] = ttlen;
    ++inDeg[v];
    ++ttlen;
}

pair<int*,int> bfs(){
    int res[N] , len = 0;
    int q[N], hd = 0, tl = 0;
    for(int i = 1 ; i <= n ; ++ i){
        if(inDeg[i] == 0){
            q[tl++] = i;
            res[len++] = i;
        }
    }
    while(hd!=tl){
        int node = q[hd++];
        int link = head[node];
        while(link!=-1){
            --inDeg[ val[link] ];
            if(inDeg[ val[link] ] == 0){
                res[len++] = val[link];
                q[tl++] = val[link];
            }
            link = nxt[link];
        }
    }
    return {res,len};
}

int main(){
    memset(head, 0xff, sizeof head);
    cin >> n >> m;
    ttlen = 0;
    while(m--){
        int u, v; cin >> u >> v;
        add(u,v);
    }
    pair<int*,int> pr = bfs();
    if(pr.second !=n) cout<<-1<<endl;
    else{
        for(int i = 0 ; i < pr.second ; ++ i)
            cout<< pr.first[i]<<' ';
        cout<<endl;
    }
    return 0;
}

Dijkstra求单源最短路

单源最短路指的是,从一个点出发,求到所有点的最短路。

边权都 > 0 情况下的最短路

朴素Dijkstra算法 O ( n 2 ) O(n^2) O(n2)(适用稠密图 边数 m m m > 点数 n 2 n^2 n2

算法一共会经过n层循环,每个循环都能确定一个路径最短的点,n个循环确定n个。
在循环内,首先 findMin,找到一个 未被访问 且 离源点最近的一个点,把它标记为访问过,然后更新它所能到达的节点的路径。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 505, null = 0x3f3f3f3f;

int n, m; // 点数和边数
int matrix[N][N]; // 邻接矩阵
int res[N];  // 节点1~N的最短距离
bool visited[N]; //表示节点是否已经确认最短的

void dijkstra(){
    memset(res, 0x3f, sizeof res);
    res[1] = 0;
    for(int i = 0 ; i < n ; ++ i){
        //进行n次循环,每次都能通过findmin 确定一个最短的
        
        //findmin
        int tp = 0;
        for(int j = 1 ; j <= n ; ++ j){
            if(!visited[j]){
                if(tp == 0 || res[j] < res[tp]) tp = j;
            }
        }
        visited[tp] = 1;
        
        // 更新 tp 所能达的点
        for(int j = 1 ; j <= n ; ++ j){
            if(!visited[j] && res[tp] + matrix[tp][j] < res[j]) 
                res[j] = res[tp] + matrix[tp][j];
        }
    }
    
}

int main(){
    cin >> n >> m;
    memset(matrix, 0x3f, sizeof matrix);
    for(int i = 0 ; i < m ; ++ i){
        int u, v, w;
        cin >> u >> v >> w;
        if(u == v) continue; // 去点自环
        if(w < matrix[u][v]) matrix[u][v] = w;  // 重边中,取最小的那个
    }
    dijkstra();
    cout<< (res[n]==null?-1:res[n]) << endl;
    return 0;
}

堆优化的Dijkstra算法 O ( m ∗ l o g n ) O(m*logn) O(mlogn)(适用稀疏图)

堆优化版的Dij算法和朴素版的相比,使用了一个堆来加快findMin的过程。
在循环内部的findMin时,我们使用一个堆,那这里的复杂度就成了O(1),但是在更新一个节点的邻接点距离时,会往堆里面插入数据,这里的复杂度变成了O(logn),因为一共会遍历所有的边一次,所以这里加起来是O(m*logn)

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2e5, null = 0x3f3f3f3f;
typedef pair<int,int> pii;
int n, m; // 点数和边数
int g[N], nxt[N], len;
pii val[N]; // pii 存的是 节点,距离
int res[N];  // 节点1~N的最短距离
bool visited[N]; //表示节点是否已经确认最短的
void add_edge(int u, int v, int w){
    val[++len] = {v,w};
    nxt[len] = g[u];
    g[u] = len;
}
class mycmp{
public:
    bool operator()(pii & a, pii & b){
        return a.second > b.second;
    }
};
void dijkstra_heap(){
    memset(res, 0x3f, sizeof res); // 初始化到所有点的距离为正无穷
    res[1] = 0;
    priority_queue<pii,vector<pii>,mycmp> pq;
    pq.push({1,0});
    while(!pq.empty()){
        pii tp = pq.top(); pq.pop();
        // cout<< tp.first<< ": "<<endl;
        if(visited[tp.first]) continue;
        visited[tp.first] = 1;
        //更新邻接边
        int nt = g[tp.first];
        while(nt!=-1){
            //如果从源点到 确认点tp.first的距离 +  确认点tp.first到邻居nt的距离
            //   小于  源点到 邻居nt的距离,则更新到小根堆里面
            if(res[val[nt].first] > tp.second + val[nt].second){
                res[val[nt].first] = tp.second + val[nt].second;// 同时也更新到res数组
                pq.push( {val[nt].first , tp.second + val[nt].second} );
            }
            nt = nxt[nt];
        }
        
    }
}

int main(){
    cin >> n >> m;
    len = 0;
    memset(g, -1, sizeof g);
    for(int i = 0 ; i < m ; ++ i){
        int u, v, w;
        cin >> u >> v >> w;
        if(u == v) continue; // 去掉自环
        add_edge(u,v,w);  // 重边不用管
    }
    dijkstra_heap();
    cout<< (res[n]==null?-1:res[n]) << endl;
    return 0;
}

存在负权边情况下的最短路

Bellman-ford 算法 O ( n ∗ m ) O(n*m) O(nm)

适用性比较好,能解决负环问题,能解决有限条边的情况。有限条边指的是,从1~n最多经过k条边时的最短路是多少。
其算法思路是,首先对于距离dist矩阵,1号点是更新了的。
然后,经过k次循环进行边更新(k表示的就是最多经过多少条边,那就更新几次)
在边更新过程中,依次遍历所有的边(所以bellman-ford算法可以直接用数组存边,而不需要表示成图),如果边的起始点dist[u]为inf,则不更新,否则,如果dist[u] + weight < dist[v],那就更新dist[v]。注意这里需要用一个数组来存储之前的dist才能实现更新过程。

进行了k次循环之后,如果dist[n] == inf, 则说明没有k步到n的路; 如果k取n-1,则就是从1~n的最短路。
如果图中存在负环,这个结果仍然正确。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e4+10;
int dist[520], predist[520], edge[N][3], len;
int n,m,k;
// bellmanford算法 可以解决 负权、负环、有限条边 的情况
// spfa 虽然在复杂度等更优,但是不能解决负环、有限条边 的情况
void bellmanford(){
    dist[1] = 0;
    for(int i = 0 ; i < k ; ++ i){1
        memcpy(predist, dist, sizeof dist);
        for(int j = 0; j < len;++j){
            //遍历所有边
            int u = edge[j][0], v = edge[j][1], w = edge[j][2];
            if(predist[u]!=0x3f3f3f3f && dist[v] > predist[u]+w) dist[v] = predist[u]+w;
        }
    }
}
int main(){
    cin >> n >> m >> k;
    len = 0;
    memset(dist, 0x3f, sizeof dist);
    while(m--){
        int u, v, w;
        cin>> u >> v >> w;
        edge[len][0] = u, edge[len][1] = v, edge[len][2] = w;
        ++len;
    }
    bellmanford();
    if(dist[n] == 0x3f3f3f3f) cout<< "impossible" << endl;
    else cout<< dist[n] << endl;
    return 0;
}

spfa算法 一般 O ( m ) O(m) O(m) 最坏 O ( n ∗ m ) O(n*m) O(nm)

spfa算法 其实是对bellman-ford算法的优化,bellman-ford算法在每次循环中,都依次更新所有的边。而spfa算法通过一个队列来存储能更新的边,减少时间复杂度。
其思想是,用一个队列q来存储能更新的点,然后依次遍历队列中的点,更新其邻接点的dist,如果邻接点的dist减小了,并且这个邻接点不在可更新队列q中(通过一个bool数组查询),则将这个邻接点也添加到队列中。依次遍历队列,直至q为空。

#include <iostream>
#include <cstring>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5+10, inf = 0x3f3f3f3f;
//需要遍历一个节点的所有临边,加上数据,这里用链表
int g[N], nxt[N], len, dist[N], n, m;
pii val[N];
int decrease[2*N], head, tail; // 存储距离降低了的点
bool indec[N];
void spfa(){
    //spfa 算法 是对bellmanford算法的优化
    dist[1] = 0;
    decrease[tail++] = 1;
    indec[1] = 1;
    // 当 降低节点 队列不空的时候
    while(head != tail){
        // 弹出队头,并且设置节点未访问
        int src = decrease[head++];
        int nt = g[ src ];
        indec[src] = 0;
        // 遍历邻接链表,更新每个邻接点的值
        while(nt!=-1){
            int node = val[nt].first, wei = val[nt].second;
            if(dist[src] + wei < dist[node]){
                //如果值更小,那就更新dist
                dist[ node ] = dist[src] + wei;
                //如果节点不在decrease里,就加进去,如果在那就不用加
                if(!indec[ node ]){
                    decrease[tail++] = node;
                    indec[ node ] = 1;
                }
            }
            nt = nxt[nt];
        }
    }
}
int main(){
    memset(dist, inf, sizeof dist);
    memset(g,-1,sizeof g);
    head = tail = 0;
    len = 0;
    cin >> n >> m;
    while(m--){
        int u, v, w;
        cin >> u >> v >> w;
        ++len;
        val[len] = {v,w};
        nxt[len] = g[u];
        g[u] = len;
    }
    spfa();
    if(dist[n] == inf) cout<<"impossible"<<endl;
    else cout<< dist[n] << endl;
    return 0;
}
spfa算法判断图中是否有负环

spfa算法判断负环的思路和spfa算法相同,它通过再添加一个pathLen数组来存储1~n节点最短路的长度。如果某次更新使某个节点的最短路的长度>=n了,则说明有负环。
需要注意的是,如果仍然从1开始更新,则找到的是1 ~ i 的路上有没有负环,如果不存在1~k的通路,则找不到k及其之后的负环了。所以找负环算法需要改变一下初始化的思路,首先将所有的点的距离都更新成某一个任意的数,然后将所有的点都添加到可更新队列,再进行更新,当某个dist减小的时候,除了更新对应的dist,还更新一下pathLen数组:pathLen[v] = pathLen[u]+1;

/* 
spfa算法找负环,只需要添加一个pathLen[N]数组来统计每个点的最短路长度
如果有点的最短路长度经过更新, >= 节点数了,则说明进入负环了,因为总共才n个点。
*/
#include <bits/stdc++.h> // 万能头文件
using namespace std;
typedef pair<int, int> pii;
const int N = 2100, M = 1e4+10;
pii val[M];
int nxt[M], len, g[N], pathLen[N], dist[N];
int n, m;
queue<int> q;
bool inq[N];

void add(int u, int v, int w){
    ++len; val[len] = {v,w};
    nxt[len] = g[u]; g[u] = len;
}

bool spfa_find_circle(){
    dist[1] = 0;
    for(int i = 1; i<= n ; ++ i){
        q.push(i);
        inq[i] = 1;
    }
    while(!q.empty()){
        int src = q.front(); q.pop();
        inq[src] = 0;
        int nt = g[src];
        while(nt!=-1){
            int node = val[nt].first, wei = val[nt].second;
            if(dist[src] + wei < dist[node]){
                dist[node] = dist[src] + wei;
                pathLen[node] = pathLen[src] + 1;
                if(pathLen[node] >= n) return 1;
                if(!inq[node]){
                    q.push(node);
                    inq[node] = 1;
                }
            }
            nt = nxt[nt];
        }
    }
    return 0;
}

int main(){
    // 优化cin cout
    ios::sync_with_stdio(false);
    cin.tie(NULL);  cout.tie(NULL);
    cin >> n >> m;
    len = 0;
    memset(g, -1, sizeof g);
    memset(dist, 0x11, sizeof dist);
    while(m--){
        int u, v, w; cin >> u >> v >> w;
        add(u, v, w);
    }
    if(spfa_find_circle()) cout << "Yes" << endl;
    else cout << "No" << endl;
    return 0;
}

多源汇最短路算法

多源最短路指的是,给定出发源和终止源,求出发源到终止源的最短路。

Floyd算法 O ( n 3 ) O(n^3) O(n3)

Floyd算法是一个基于 DP 思想的算法。
其思想是(待补充):
用邻接矩阵matrix[N][N]表示一个图,同时在这个邻接矩阵上进行最短路的更新。
初始化,matrix[i][i]为0,这样可以删掉自环,然后,添加边,如果有i -> j的边weight,则matrix[i][j]为min(weight, matrix[i][j]),这样可以取重边中最小的一条边。
第一层循环,遍历节点 k ,表明的是,其他的u、v经过节点 k 时的最短路
第二层循环 i 和第三层循环 j 遍历整个邻接矩阵,如果matrix[i][k] 或者 matrix[k][j]为inf,则不需要更新了,正无穷再加或者减都是正无穷,如果二者都不是inf,再进行比较,如果matrix[i][k]+matrix[k][j] < matrix[i][j],则表明,从 i 经过 k 到 j 经过的路径要比之前的短,不管之前的经过哪里,反正小,所以更新matrix[i][j] 为 matrix[i][k] + matrix[k][j]

#include <bits/stdc++.h>
using namespace std;
const int N = 210, inf = 0x3f3f3f3f;
int matrix[N][N];
int n, m, q;
void floyd(){
    for(int k = 1 ; k <= n ; ++ k){
        for(int i = 1 ; i <= n ; ++ i){
            for(int j = 1 ; j <= n ; ++ j){
                if(matrix[i][k] != inf && matrix[k][j]!=inf){
                    matrix[i][j] = min(matrix[i][j], matrix[i][k] + matrix[k][j]);
                }
            }
        }
    }
}
int main(){
    // 优化cin cout
    ios::sync_with_stdio(false);
    cin.tie(NULL);  cout.tie(NULL);
    cin >> n >> m >> q;
    //init matrix
    for(int i = 1 ; i <= n; ++ i){
        for(int j = 1 ; j <= n ; ++ j){
            if(i == j) matrix[i][j] = 0;
            else matrix[i][j] = inf;
        }
    }
    while(m--){
        int u, v, w;
        cin >> u >> v >> w;
        if(u != v)// 避免自环
            matrix[u][v] = min(w, matrix[u][v]); // 避免重边,这里选最小的
    }
    floyd();
    while(q--){
        int u, v; cin >> u >> v;
        if(matrix[u][v] == inf) cout<< "impossible" << endl;
        else cout<< matrix[u][v] << endl;
    }
    return 0;
}

图的最小生成树问题

给定一个可能带有自环、圈的图,给出其最小生成树

prim算法(适用于稠密图, m > n 2 m>n^2 m>n2

通过邻接矩阵的形式存储一个图。
定义 S 表示已经在生成树中的点,dist为其他未在生成树中的点到S中的最小距离。
和Dijkstra算法差不多,都是进行n次循环,每次循环能加入S中一个节点。
在循环中,首先就是findMin,找到一个里S集合最近的节点,添加到S中,然后更新这个点的邻边(因为只有这个点有出边,会影响其他点到集合的距离,所以只需要更新这个点的邻接点就行)。

#include <bits/stdc++.h>
using namespace std;
const int N = 520;
int adj[N][N], dist[N];//dist记录点到已扩散集合的距离
bool inS[N]; // 记录点是否已经添加到最小生成树集合
int res,n,m;
void add(int u, int v, int w){
    if(u == v) return;
    adj[u][v] = min(adj[u][v], w);
}
void prim(){
    /*
    朴素prim算法 采用邻接矩阵的形式存储图,时间复杂度是 O(n^2),适用于稠密图(m>n^2)
    还有堆优化版的prim算法,通过堆优化findmin  O(mlogn),但是因为有kruskal算法,所以堆优化prin不常用
    */
    //for循环n次,每次都能添加进集合一个点
    int lenS = 0;
    dist[1]=0;
    for(int i = 1 ; i <= n ; ++ i){
        // findmin 找到距离集合最近的点
        int minidx = 0, minval = 0x3f3f3f3f;
        for(int i = 1 ; i <= n ; ++ i){
            if(!inS[i] && dist[i] < minval){
                minval = dist[i], minidx = i;
            }
        }
        if(minidx == 0) continue;
        ++lenS; inS[minidx]=1;
        res+=minval;
        for(int i = 1 ; i <= n ; ++ i){
            if(!inS[i] && dist[i] > adj[minidx][i]){
                dist[i] = adj[minidx][i];
            }
        }
    }
    // cout<<lenS<<endl;
    if(lenS < n) cout<<"impossible"<<endl;
    else cout<< res<<endl;
}
int main(){
    //init
    res = 0;
    memset(adj,0x3f,sizeof adj);
    memset(dist,0x3f,sizeof dist);
    cin>>n>>m;
    while(m--){
        int u,v,w; cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    prim();
    return 0;
}

Kruskal算法(适用于稀疏图, m < n 2 m<n^2 m<n2)

设计的其他知识点(并查集、快排)
首先是对所有的边进行排序,按照边权升序的方式。
然后从最小权的边开始遍历,如果边的起始、终止节点不在一个集合中,那就可以添加这个边,同时将这两个节点合并在一个集合里。每添加一条边,都进行一次计数,对于一个树来说,边数=节点数-1,那么如果最后添加的次数!=n-1,那就说明不连通,就不存在最小生成树。

#include <iostream>

using namespace std;
const int N = 1e5+10, M = 2*N;
int n, m;
int edges[M][3], fa[N];
int res;
void fastSort(int L, int R){
    if(L>=R) return;
    int tar = edges[(L+R+1)/2][2];
    // cout<<tar<<endl;
    int ll = L-1, rr = R+1;
    while(ll < rr){
        do ++ll; while(edges[ll][2] < tar);
        do --rr; while(edges[rr][2] > tar);
        if(ll < rr){
            for(int i = 0 ; i <= 2 ; ++ i)
                swap(edges[ll][i], edges[rr][i]);
        }
    }
    fastSort(L,ll-1);
    fastSort(ll, R);
}

int findFa(int a){
    if(fa[a] != a) fa[a] = findFa(fa[a]);
    return fa[a];
}

int main(){
    cin >> n >> m;
    int len = 0;
    while(m--){
        int u, v, w; cin >> u >> v >> w;
        edges[len][0] = u, edges[len][1] = v, edges[len][2] = w;
        ++len;
    }
    //先对所有的边按照权重升序排序
    fastSort(0,len-1);
    //并查集,先初始化所有的点为自己的集合
    for(int i = 1 ; i <= n ; ++ i){
        fa[i] = i;
    }
    //然后按照顺序,集合合并,如果二者的父不同,则可以添加这个边
    res = 0;
    int cnt = 0; // cnt表示添加进最小生成树多少条边了
    for(int i = 0 ; i < len ; ++ i){
        int ffa = findFa(edges[i][0]), ffb = findFa(edges[i][1]);
        if( ffa != ffb ){
            //更新结果
            res+=edges[i][2];
            fa[ffb] = ffa;
            //维护数量
            ++cnt;
        }
    }
    //如果最后添加边的数量不是n-1,(树的边数为n-1)那就说明不连通,输出impossible
    if(cnt!=n-1) cout<<"impossible"<<endl;
    else cout<<res<<endl;
    return 0;
}

染色法判定二分图

染色法是基于深度优先遍历的一个算法。算法基于 二分图不含奇圈的定理。
先for循环给每一个节点染 1 色(这里是为了避免非连通无法遍历到的情况),然后去遍历他的子节点,但是染的色逆转一下(如果染色1、2,那可以用3- i 来实现染色的逆转),如果子节点没染色,或者已经染色但恰好是我想染的色,那就成功,回溯,否则,染色冲突。
一旦有染色冲突的,那就说明检测到了奇圈,则这个图不是二分图。

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5+10, M = 2*N;
//用 邻接链表存储图
int g[N], val[M], nxt[M], len;
int n, m;
int color[N];// 记录每个点的着色
bool dfs(int nn, int c){
    if(!color[nn]){
        //还未着色
        color[nn] = c;
        for(int i = g[nn]; i!=-1 ; i = nxt[i]){
            if(!dfs(val[i],3-c)){
                return false;
            }
        }
        
        return true;
    }
    else{
        return color[nn] == c;
    }
}
int main(){
    memset(g,-1,sizeof g);
    cin >> n >> m;
    for(int i = 0 ; i < m ; ++ i){
        int u, v; cin >> u >> v;
        // 加边 u -> v
        val[len] = v;
        nxt[len] = g[u];
        g[u] = len++;
        //加边 v -> u
        val[len] = u;
        nxt[len] = g[v];
        g[v] = len++;
    }
    /*
    这里是为了避免 多个连通分支的情况,
    从一个未开始染色的点开始  进行dfs染色
    如果中间出现了染色冲突的情况,则染色失败,不是二分图
    如果没有出现,则说明 染过色的都是正确的,那我就从下一个没染色的开始,而不管染过色的
    */
    for(int node = 1 ; node <=n ; ++node){
        if(!color[node] && !dfs(node,1)){
            cout<< "No"<<endl;
            return 0;
        }
    }
    cout<<"Yes"<<endl;
    return 0;
}

匈牙利算法

给定一个二分图,寻找其最大匹配数量(所有边的起始和终止节点都不重合),假定二分图分为左边的节点共 n1 个和右边的节点共 n2 个。然后给定m条边。
匈牙利算法是,选一个类的节点,比如以左边为基础,进行遍历。
当遍历到某一个点的时候,依次遍历它的邻接点,如果邻接点没有和其他点匹配,或者邻接点已经匹配了,但是其他的点能再换点匹配,那就认为这个点可以被匹配。

看邻接点能不能换点匹配的实现方法是递归,见程序

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

const int N = 520, M = 1e5+10;

int n1,n2,m;
// 邻接链表存储图
int g[N], nxt[M], val[M], len;

int match[N], order[N];//match代表已经匹配,order代表预定

bool findGF(int i){
    int nt = g[i];
    for(; nt != -1; nt = nxt[nt]){
        if(!order[val[nt]]){
            order[val[nt]] = 1;
            if(match[val[nt]] == 0 || findGF(match[val[nt]]) ){
                match[val[nt]] = i;
                return true;
            }
        }
    }
    return false;
}

int main(){
    memset(g,-1, sizeof g);
    cin >> n1 >> n2 >> m;
    for(int i = 0 ; i != m ; ++ i){
        int u, v; cin >> u >> v;
        // 只添加单向边,将其视为单边  从左到右就可以
        val[len] = v;
        nxt[len] = g[u];
        g[u] = len++;
    }
    int m = 0;
    for(int i = 1 ; i <= n1 ; ++ i){
        // 重置order,表示的是为 第 i 个节点findGF时有没有被尝试预定过
        memset(order,0,sizeof order);
        if(findGF(i)){
            ++m;
        }
    }
    cout<< m << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值