zoukankan      html  css  js  c++  java
  • 复习1-图论模板

    1.最短路

    图全为正权使用Dijkstra,有负权用SPFA,Bellman-Ford稍加了解即可

    void spfa(){
        queue<int> q;
        for(int i = 1;i <= n;i++) d[i] = 0x7fffffff;
        q.push(s);vis[s] = 1;d[s] = 0;
        while(!q.empty()){
            int x = q.front();q.pop();vis[x] = 0;
            for(int i = head[x];i;i = G[i].pre){
                int v = G[i].to,w = G[i].v;
                if(d[v] > d[x] + w){
                    d[v] = d[x] + w;
                    if(!vis[v]){
                        vis[v] = 1;
                        q.push(v);
                    }
                }
            }
        }
    }
    SPFA
    struct HeapNode{
        int u,d;
        bool operator < (const HeapNode& rhs) const{
            return d > rhs.d;
        }
    };
    
    void Dijkstra()
    {
        priority_queue<HeapNode> q;
        for(int i = 1;i <= n;i++)
        d[i] = INF;
        d[s] = 0;
        q.push((HeapNode){s,d[s]});
        while(!q.empty()){
            HeapNode x = q.top();q.pop();
            int u = x.u;
            if(x.d > d[u]) continue;
            for(register int i = last[u];i >= 0;i = e[i].next)
            {
                int v = e[i].v,w = e[i].w;
                if(d[u] + w < d[v]){
                    d[v] = d[u] + w;
                    q.push((HeapNode){v,d[v]});
                }
            }
        }
    }
    Dijkstra+堆优化
    /*
        Bellman-Ford算法 题解上的
    */
    
    #include<iostream>
    using namespace std;
    const int maxx=10001;
    int n,m,s,dis[maxx],w[500001],num[maxx],f[maxx][maxx/10][2],a=0;
    int main(){
        ios::sync_with_stdio(false);
        cin>>n>>m>>s;
        for(int i=1;i<=n;i++) dis[i]=400;
        for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++) w[i]=400;
        for(int i=1;i<=m;i++){
            int x,y,v;
            cin>>x>>y>>v;
            f[x][++num[x]][0]=y;
            f[x][num[x]][1]=i;
            w[i]=v;
        }
        dis[s]=0;
        while(a<=50){ //循环大法好
            for(int i=1;i<=n;i++)
            for(int j=1;j<=num[i];j++)
            dis[f[i][j][0]]=min(dis[f[i][j][0]],dis[i]+w[f[i][j][1]]);
            a++;
        }
        for(int i=1;i<=n;i++) if(dis[i]==400) cout<<2147483647<<' '; else cout<<dis[i]<<' ';
        return 0;
    }
    Bellman-Ford

    2.最小生成树

    主要掌握Kruskal算法,Prim算法稍加了解即可

    /*
         适用于稀疏图 
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    int p[5005];
    int n,m,num = 0;
    
    struct node
    {
        int x,y,z;
    }e[200005];
    
    int cmp(node a,node b)
    {
        return a.z < b.z;
    }
    
    int find(int x)
    {
        return p[x] == x ? x : p[x] = find(p[x]);
    }
    
    int Kruskal()
    {
        for(int i = 1;i <= n;i++) p[i] = i;
        sort(e+1,e+m+1,cmp);
        int ans = 0,cnt = 0;
        for(int i = 1;i <= m;i++)
        {
            int x = find(e[i].x);
            int y = find(e[i].y);
            if(x != y)
            {
                p[x] = y;
                ans += e[i].z;
                cnt++;
            }
            if(cnt == n-1) break;
        }
        return ans;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= m;i++)
        {
            scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
        }
        printf("%d
    ",Kruskal());
        return 0;
    }
    Kruskal算法
    /*
        适用于稠密图(然而没啥用)
    */
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 5010;
    const int INF = 0x7fffffff;
    
    int n,m,cnt,head[maxn],dis[maxn],vis[maxn];
    
    struct node{
        int to,v,pre;
    }e[400010];
    
    void addedge(int from,int to,int v){
        e[++cnt].pre=head[from];
        e[cnt].to=to;
        e[cnt].v=v;
        head[from]=cnt;
    }
    
    int Prim(){
        memset(dis,0x3f,sizeof(dis));
        dis[1]=0;int ans = 0;
        for(int i = 1;i <= n;i++){
            int minn = INF,k = 0;
            for(int j = 1;j <= n;j++){
                if(!vis[j] && dis[j] < minn){
                    minn = dis[j];
                    k = j;
                }
            }
            if(minn == INF)break;
            vis[k] = 1;
            for(int j = head[k];j;j = e[j].pre){
                int f = e[j].to;
                if(!vis[f])
                    dis[f] = min(dis[f],e[j].v);
            }
        }
        for(int i = 1;i <= n;i++) ans += dis[i];
        return ans;
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        int x,y,z;
        for(int i = 1;i <= m;i++){
            scanf("%d%d%d",&x,&y,&z);
            addedge(x,y,z);addedge(y,x,z);
        }
        printf("%d
    ",Prim());
        return 0;
    }
    Prim算法

    3.LCA

    主要理解倍增法,树剖也可以了解,Tarjan就算了

    /*
        倍增写法 常数略大
        我觉得不太好理解 所以我不用倍增了
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int MAXN = 500010;
    
    int deep[MAXN],f[MAXN][25],lg[MAXN],head[MAXN],cnt;
    int n,m,s;
    
    struct node{
        int to,pre;
    }G[MAXN*2];
    
    void add(int from,int to){
        G[++cnt].to = to;
        G[cnt].pre = head[from];
        head[from] = cnt;
    }
    
    inline int read() {
         int x = 0,m = 1;
         char ch;
         while(ch < '0' || ch > '9')  {if(ch == '-') m = -1;ch = getchar();}
         while(ch >= '0' && ch <= '9'){x = x*10+ch-'0';ch=getchar();}
         return m * x;
     }
    
    inline void dfs(int u)
    {
        for(int i = head[u];i;i = G[i].pre)
        {
            int v = G[i].to;
            if(v != f[u][0])
            {
                f[v][0] = u;
                deep[v] = deep[u] + 1;
                dfs(v);
            }
        }
    }
    
    inline int lca(int u,int v)
    {
        if(deep[u] < deep[v]) swap(u,v);
        int dis = deep[u] - deep[v];
        for(register int i = 0;i <= lg[n];i++)
        {
            if((1 << i) & dis) u = f[u][i];
        }
        if(u == v) return u;
        for(register int i = lg[deep[u]];i >= 0;i--)
        {
            if(f[u][i] != f[v][i])
            {
                u = f[u][i];v = f[v][i];
            }
        }
        return f[u][0];
    }
    
    inline void init()
    {
        for(register int j = 1;j <= lg[n];j++)
        {
            for(register int i = 1;i <= n;i++)
            {
                if(f[i][j-1] != -1)
                f[i][j] = f[f[i][j-1]][j-1];
            }
        }
    }
    
    int main()
    {
        int x,y,a,b;
        n = read();m = read();s = read();
        for(register int i = 1;i <= n;i++)
        {
            lg[i] = lg[i-1] + (1 << lg[i-1] + 1 == i);
        }
        for(register int i = 1;i <= n-1;i++)
        {
            x = read();y = read();
            add(x,y);add(y,x);
        }
        dfs(s);
        init();
        while(m--)
        {
            a = read();b = read();
            printf("%d
    ",lca(a,b));
        }
        return 0;
    }
    倍增
    /*
        这种树剖写法好理解(好背)
        虽然代码比倍增略长 但是也比倍增快
        简直完美2333333
        所以以后就用这种方法辣
    */
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 500005;
    
    int fa[maxn],top[maxn],id[maxn],son[maxn],depth[maxn],size[maxn];//树剖要用的所有数组
    int n,m,s,head[maxn],cnt;
    
    struct node{
        int to,pre;
    }G[maxn*2];
    
    void addedge(int from,int to){
        G[++cnt].to = to;
        G[cnt].pre = head[from];
        head[from] = cnt;
    }
    
    void dfs1(int x){
        size[x] = 1;
        for(int i = head[x];i;i = G[i].pre){
            int cur = G[i].to;
            if(cur == fa[x]) continue;
            depth[cur] = depth[x] + 1;
            fa[cur] = x;
            dfs1(cur);
            size[x] += size[cur];
            if(size[cur] > size[son[x]]) son[x] = cur;
        }
    }
    
    void dfs2(int x,int t){
        top[x] = t;
        if(son[x]) dfs2(son[x],t);
        for(int i = head[x];i;i = G[i].pre){
            int cur = G[i].to;
            if(cur != fa[x] && cur != son[x])
                dfs2(cur,cur);
        }
    }
    
    int lca(int x,int y){
        while(top[x] != top[y]){
            if(depth[top[x]] < depth[top[y]]) swap(x,y);
            x = fa[top[x]];
        }
        if(depth[x] > depth[y]) swap(x,y);
        return x;
    }
    
    int main(){
        int x,y,a,b;
        scanf("%d%d%d",&n,&m,&s);
        for(int i = 1;i < n;i++){
            scanf("%d%d",&x,&y);
            addedge(x,y);addedge(y,x);
        }
        dfs1(s);
        dfs2(s,s);
        while(m--){
            scanf("%d%d",&a,&b);
            printf("%d
    ",lca(a,b));
        }
        return 0;
    }
    树剖
    /*
        Tarjan算法(题解) 常数挺大的,不推荐,容易被卡
    */
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <algorithm>
    #include <cctype>
    #include <cstring>
    #include <queue>
    #include <map>
    #define ll long long 
    #define ri register int 
    #define ull unsigned long long
    using namespace std;
    const int maxn=500005;
    const int inf=0x7fffffff;
    template <class T>inline void read(T &x){
        x=0;int ne=0;char c;
        while(!isdigit(c=getchar()))ne=c=='-';
        x=c-48;
        while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48;
        x=ne?-x:x;
        return ;
    }
    int n,m,s,t;
    struct Edge{
        int ne,to;
    }edge[maxn<<1];
    struct QU{
        int d,id;
        QU(int x,int y){d=x,id=y;}
        QU(){;}
    };
    vector <QU>q[maxn];
    int h[maxn],num_edge=0,ans[maxn];
    inline void add_edge(int f,int to){
        edge[++num_edge].ne=h[f];
        edge[num_edge].to=to;
        h[f]=num_edge;
        return;
    }
    int fa[maxn],vis[maxn];
    int get(int x){
        if(fa[x]!=x)fa[x]=get(fa[x]);
        return fa[x];
    }
    void dfs(int cur){
        int u,v;
        vis[cur]=1;
        for(ri i=h[cur];i;i=edge[i].ne){
            v=edge[i].to;
            if(vis[v])continue;
            dfs(v);
            fa[v]=cur;//dfs后再合并
        }
        for(ri i=0;i<q[cur].size();i++){
            u=q[cur][i].d,v=q[cur][i].id;
            if(vis[u]==2){
                ans[v]=get(u);
            }
        }
        vis[cur]=2;//dfs过
        return ;
    }
    int main(){
        int x,y;
        read(n),read(m),read(s);
        for(ri i=1;i<n;i++){
            read(x),read(y);
            add_edge(x,y);
            add_edge(y,x);
            fa[i]=i;
        }fa[n]=n;
        for(ri i=1;i<=m;i++){
            read(x),read(y);
            //q[x].push_back(y);q[y].push_back(x);
            q[x].push_back(QU(y,i));
            q[y].push_back(QU(x,i));
        }
        dfs(s);
        for(ri i=1;i<=m;i++){
            printf("%d
    ",ans[i]);
        }
        return 0;
    }
    Tarjan

    4.二分图最大匹配

    要会

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 1e3+5;
    
    vector<int> G[maxn];
    int link[maxn],vis[maxn],n,m,e;
    
    bool dfs(int x){
        for(int i = 0;i < G[x].size();i++){
            int v = G[x][i];
            if(!vis[v]){
                vis[v] = 1;
                if(!link[v] || dfs(link[v])){
                    link[v] = x;return true;
                }
            }
        }
        return false;
    }
    
    int main()
    {
        int x,y;
        scanf("%d%d%d",&n,&m,&e);
        for(int i = 1;i <= e;i++){
            scanf("%d%d",&x,&y);
            if(x <= n && y <= m) G[x].push_back(y);
        }
        int ans = 0;
        for(int i = 1;i <= n;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)) ans++;
        }
        printf("%d
    ",ans);
        return 0;
    }
    匈牙利算法

    5.Tarjan

    全部要会

    vector<int> G[maxn];  //vector邻接表存图 
    int pre[maxn],low[maxn],sccno[maxn],cnt,scccnt;
    //pre[i]表示节点i被搜到的次序,lowlink[i]表示i及其后代能追溯到的最早的点v的
    //pre[v]值,sccno[i]就是i所在的强连通分量的编号,dfs_clock表示第几次dfs,
    //scc_cnt表示找到的强连通分量序号的临时值 
    stack<int> S;//存储dfs到的每一个点 
    
    void dfs(int u)
    {
        pre[u] = low[u] = ++cnt;//先把pre和lowlink初始化为dfs的时间戳 
        S.push(u);
        for(int i = 0;i < G[u].size();i++)//遍历与点u相连的所有点 
        {
            int v = G[u][i];//取点 
            if(!pre[v]){//如果点v没有遍历过 
                dfs(v);//深搜 
                low[u] = min(low[u],low[v]);//向上合并lowlink的值 
            } else if(!sccno[v])//此时v已经搜过,但是不属于任何一个scc,那么就说明已经形成了环 
            {
                low[u] = min(low[u],pre[v]);
            }
        }
        if(low[u] == pre[u]){//如果u为最先搜到的点,它就是这个scc的根节点 
        scccnt++; 
        for(;;)
        {
            int x = S.top();S.pop();//取一个搜过的点 
            sccno[x] = scccnt;//它属于这个强联通分量 
            if(x == u) break;//直到栈中 
             }
        }
     } 
    
     void find_scc(int n)
     {
        cnt = scccnt = 0;
        memset(sccno,0,sizeof(sccno));
        memset(pre,0,sizeof(pre));
        for(int i = 1;i <= n;i++)
        if(!pre[i]) dfs(i);
     }
    求强连通分量
    /*
         易得出状态转移为val[v] = max(val[v],val[pre] + a[v]) 
         于是通过tarjan算法把所有环缩为一点,跑一遍DAG上的DP即可 
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 10e5+5;
    
    int scc[maxn],dfn[maxn],low[maxn],stac[maxn];
    int a[maxn],head1[maxn],head2[maxn],vis[maxn];
    int val[maxn],cnt,cnt1,scc_clock,top,n,m,tot,scc_amount;
    
    struct node{
        int to,from,pre;
    }g1[maxn*2],g2[maxn*2];//g1为原来的图,g2为缩点后的图 
    
    void add1(int from,int to){
        g1[++cnt].from = from;
        g1[cnt].to = to;
        g1[cnt].pre = head1[from];
        head1[from] = cnt;
    } //对应g1的add操作 
    
    void add2(int from,int to){
        g2[++cnt1].from = from;
        g2[cnt1].to = to;
        g2[cnt1].pre = head2[from];
        head2[from] = cnt1;
    } //对应g2的add操作 
    
    void tarjan(int x){
        low[x] = dfn[x] = ++scc_clock;//初始化为时间戳 
        stac[++top] = x;vis[x] = 1;//入栈、标志数组设为1 
        for(register int i = head1[x];i;i = g1[i].pre){//遍历与x连接的点 
            int v = g1[i].to;
            if(!dfn[v]){
                tarjan(v);
                low[x] = min(low[x],low[v]);
            }else if(vis[v]){
                low[x] = min(low[x],dfn[v]);
            }
        }//一顿tarjan的操作 
        if(low[x] == dfn[x]){
            scc_amount++;
            int y;
            while(y = stac[top--]){//出栈 
                scc[y] = x;
                vis[y] = 0;//我也不知道为啥 
                if(x == y) break;
                a[x] += a[y];//加权值 
            }
        }
    }
    
    int dfs(int u){
        val[u] = a[u];
        for(int i = head2[u];i;i = g2[i].pre){
            int v = g2[i].to;
            dfs(v);
            val[v] = max(val[v],val[u] + a[v]);
        }
    }
    
    int main(){
        int x,y;
        scanf("%d%d",&n,&m);
        for(register int i = 1;i <= n;i++){
            scanf("%d",a+i);
        }
        for(register int i = 1;i <= m;i++){
            scanf("%d%d",&x,&y);
            add1(x,y);
        }
        for(register int i = 1;i <= n;i++){
            if(!dfn[i]) tarjan(i);//tarjan算法基本操作 
        }
        for(register int i = 1;i <= m;i++){
            x = scc[g1[i].from],y = scc[g1[i].to];
            if(x != y){//如果不属于同一强连通分量,就连边 
                add2(x,y);
            }
        }
        int ans = 0;
        for(int i = 1;i <= scc_amount;i++){
            if(!val[i]){
                dfs(i);
                ans = max(ans,val[i]);
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    缩点
    /*
    无向图割点
    对该图进行一次 Tarjan 算法(这里注意在搜索树中把无向边当做有向边看。即LOW[u]=min(LOW[u],DFN[v])(v 是 u 的祖先)的条件变为(v 是 u 的祖先且 v 不是 u 的父亲))这样之后枚举搜索树上的所有边(u,v),若存在 LOW[v]>=DNF[u],则 u 是割点。
    无向图割边
    对该图进行一次 Tarjan 算法(这里注意在搜索树中把无向边当做有向边看。即LOW[u]=min(LOW[u],DFN[v])(v 是 u 的祖先)的条件变为(v 是 u 的祖先且 v 不是 u 的父亲))这样之后枚举搜索树上的所有边(u,v),若存在 LOW[v]>DNF[u],则(u,v)为割边。
    */
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 100005;
    
    int low[maxn],dfn[maxn],cut[maxn];
    int n,m,cnt;
    vector<int> G[maxn];
    
    void tarjan(int x,int father){
        int child = 0;
        low[x] = dfn[x] = ++cnt;
        for(int i = 0;i < G[x].size();i++){
            int v = G[x][i];
            if(!dfn[v]){
                tarjan(v,father);
                low[x] = min(low[x],low[v]);
                if(dfn[x] <= low[v] && x != father) cut[x] = 1;
                if(x == father) child++;
            }
            low[x] = min(low[x],dfn[v]);
        }
        if(x == father && child >= 2) cut[x] = 1;
    }
    
    int main(){
        int x,y;
        scanf("%d%d",&n,&m);
        for(int i = 1;i <= m;i++){
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
        }
        int ans = 0;
        for(int i = 1;i <= n;i++) if(!dfn[i]) tarjan(i,i);
        for(int i = 1;i <= n;i++){
            if(cut[i]) ans++;
        }
        printf("%d
    ",ans);
        for(int i = 1;i <= n;i++){
            if(cut[i]) printf("%d ",i);
        }
        return 0;
    }
    求割点

    6.树上差分

    7.拓扑排序

  • 相关阅读:
    1093 Count PAT's(25 分)
    1089 Insert or Merge(25 分)
    1088 Rational Arithmetic(20 分)
    1081 Rational Sum(20 分)
    1069 The Black Hole of Numbers(20 分)
    1059 Prime Factors(25 分)
    1050 String Subtraction (20)
    根据生日计算员工年龄
    动态获取当前日期和时间
    对计数结果进行4舍5入
  • 原文地址:https://www.cnblogs.com/bryce02/p/9931332.html
Copyright © 2011-2022 走看看