算法竞赛常用模板(图论篇)

个人整理,大多数自己写的,少数来自网络,侵删

部分代码用到了C++11的新特性(如for-each循环,auto,结构体赋值)。目前ACM和PAT支持C++17,CCF CSP认证(大学生)支持C++11。但是NOIP和蓝桥杯 不支持C++11,使用模板时请注意。(4.23 Update: 第十四届蓝桥杯支持C++11了!!)

知识点待补充T_T

有向无环图(DAG)相关

拓扑排序

定义(百度百科):

在计算机科学领域,有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点 u u u到顶点 v v v的每个有向边 u v uv uv u u u在排序中都在 v v v之前。
在图论中,由一个有向无环图(DAG)的顶点组成的序列,当且仅当满足下列条件时,才能称为该图的一个拓扑排序(英语:Topological sorting):
1.序列中包含每个顶点,且每个顶点只出现一次;
2.若A在序列中排在B的前面,则在图中不存在从B到A的路径。

实现:BFS

复杂度: O ( n + e ) O(n+e) O(n+e)

(本笔记约定:n为图的点数,e为图的边数)

模板题:AcWing1191

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) {cerr<<#x<<" = "<<(x)<<endl;}
const int N{
   102};

int n, ru[N], chu[N];
vector<int>v[N];
vector<int>topo; //拓扑序列

void toposort()
{
   
    queue<int>q;
    for(int i=1;i<=n;i++) //让所有入度为0的点先入队
        if(ru[i]==0)
            q.push(i);
    while(!q.empty())
    {
   
        int now=q.front(); q.pop();
        topo.push_back(now);
        for(auto nxt:v[now])
        {
   
            ru[nxt]--;
            if(ru[nxt]==0) //入度减为0,则入队
                q.push(nxt);
        }
    }
}

int main()
{
   
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++) //读入每个点的后继,以0结尾
    {
   
        int now; cin>>now;
        while(now)
        {
   
            ru[now]++;
            chu[i]++;
            v[i].push_back(now);
            cin>>now;
        }
    }
    toposort();
    for(auto i:topo)
        cout<<i<<" ";
    return 0;
}

Tarjan算法(有向图)

有向图的强连通分量(SCC)+缩点

模板题:洛谷P3387 缩点+DAG DP

在一个有向图中,求点权之和最大的路径

算法:先缩点,再做DP

复杂度: O ( n + e ) O(n+e) O(n+e)

//luogu P3387 缩点+DAG DP
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) {cerr<<#x<<" = "<<(x)<<endl;}

const int N{
   10005},M{
   100005};
int n,m;
int score[M]; //点权
vector<int>v[N]; //原图

int score2[M]={
   0}; //DAG点权
vector<int>dag[N]; //缩点后的DAG
int ru[N]={
   0},chu[N]={
   0}; //记录DAG中每个点的入度、出度

namespace Tarjan
{
   
    stack<int>s;
    bool inStack[N];
    int dfn[N], low[N], id[N]; 
    //dfn[i]是i的编号,low[i]表示i能到达的最小编号,id[i]是i属于的连通分量的编号
    int timeStamp=0, scc=0; //时间戳,强连通分量编号
    void tarjan(int u)
    {
   
        dfn[u] = low[u] = ++timeStamp;
        s.push(u); inStack[u] = true;
        for(auto nxt:v[u])
        {
   
            if(!dfn[nxt])
            {
   
                tarjan(nxt);
                low[u] = min(low[u], low[nxt]);
            }
            else if(inStack[nxt])
                low[u] = min(low[u], dfn[nxt]);
        }
        if(dfn[u]==low[u])
        {
   
            int now; scc++;
            do {
   
                now = s.top(); s.pop();
                inStack[now] = false;
                id[now] = scc;
            } while(now!=u);
        }
    }//end tarjan
    void suodian()
    {
   
        for(int i=1;i<=n;i++)
        {
   
            score2[id[i]] += score[i];
            for(auto nxt:v[i])
                if(id[i]!=id[nxt])
                {
   
                    dag[id[i]].push_back(id[nxt]);
                    ru[id[nxt]]++; chu[id[i]]++;
                }
        }
    } //缩点之后,id越小拓扑序越靠后
    void work()
    {
   
        for(int i=1;i<=n;i++)
            if(!dfn[i])
                tarjan(i);
        suodian();
    } //根据id排了逆拓扑序
}

namespace DP
{
   
    int f[N]={
   0}; //f[i]代表以i为终点可获得的最大值

    void work()
    {
   
        for(int i=Tarjan::scc;i>=1;i--)
        {
   
            f[i] += score2[i];
            for(auto nxt:dag[i])
            {
   
                f[nxt] = max(f[nxt],f[i]);
            }
        }
        int ans = 0;
        for(int i=Tarjan::scc;i>=1;i--)
        {
   
            if(chu[i]==0)
                ans = max(ans,f[i]);
        }
        cout<<ans;
    }
}

int main()
{
   
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>score[i];
    for(int i=1;i<=m;i++)
    {
   
        int x,y; cin>>x>>y;
        v[x].push_back(y);
    }
    Tarjan::work();
    DP::work();
    return 0;
}

有向图的最大强连通子图

模板题:洛谷P1726

题意:求有向图中的一个最大点集,其中的点互相可以到达。如果有多个,输出字典序最小的。

解法:tarjan算法,记录每个强连通分量包含的点的个数,输出点的个数最多的集合即可。

#include <bits/stdc++.h>
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值