算法学习笔记-----图论基础

基础知识

两种存储方式:邻接矩阵、邻接表。
两种遍历:dfs,bfs。
图的遍历
考虑深搜,发现出现环的情况进行不下去。
一种可行的方案是反向建边后从最后一个点往后开始 dfs 或 bfs。

#include<bits/stdc++.h>
#define endl '\n'

using namespace std;

const int maxn = 1e5+3;
vector<int> p[maxn];
int a[maxn];

void dfs(int x,int v)
{
    a[x]=v;
    for(int i=0;i<p[x].size();i++)
    {
        if(!a[p[x][i]])
        dfs(p[x][i],v);
    }
}

void solve()
{
    int n,m; cin>>n>>m;
    int u,v;
    for(int i=1;i<=m;i++) // 反向建边
    {
        cin>>u>>v;
        p[v].push_back(u);
    }
    for(int i=n;i>=1;i--)
    {
        if(!a[i])
        {
            dfs(i,i);
        }
    }
    for(int i=1;i<=n;i++)
    cout<<a[i]<<" ";
    return ; 
}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T; T=1;
    while(T--) solve();
    return 0;
}
DAG 与 拓扑排序

DAG:有向无环图。
拓扑排序:每次选入度为 0 的点,删除它和它的出边。
在这里插入图片描述
拓扑排序的结果不唯一,例如在上图中,a-e-b-c-d 或 a-b-e-c-d 或 a-b-c-e-d 都是正确的答案。
拓扑排序只能在 DAG 上进行,有向才能确定删除的顺序,无环才能保证排序过程正确进行,如果有环的话会导致排序到某一步出现没有入度为 0 的点。
同理,如果拓扑排序进行不下去了,说明这个图里有环。

最大食物链计数
求食物链的总数,必须从生产者开始,到最高级消费者结束,模拟拓扑排序的过程即可。

#include<bits/stdc++.h>
#define endl '\n'
#define int long long

using namespace std;

const int mod = 80112002;
const int maxn = 5e3+3;
int outd[maxn],ind[maxn];
int f[maxn]; // 记录以 i 为终点的方案数
vector<int>p[maxn];
queue<int>q;

void solve()
{
    int n,m;
    cin>>n>>m;
    int u,v;
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v;
        p[u].push_back(v);
        outd[u]++;
        ind[v]++;
    }
    for(int i=1;i<=n;i++)
    {
        if(!ind[i]) // 将入度为 0 的点加入队列 
        {
            q.push(i);
            f[i]=1; // 初始时,以入度为 0 的点作为子食物链的数量为 1 
        }         
    }
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=0;i<p[x].size();i++)
        {
            int y=p[x][i];
            f[y]=(f[x]+f[y])%mod;
            ind[y]--;
            if(!ind[y]) q.push(y);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(!outd[i])
        ans=(ans+f[i])%mod;
    }
    cout<<ans<<endl;
    return ;
}

signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T; T=1;
    while(T--) solve();
    return 0;
}

最长路
除了 1 之外可能还有入度为 0 的点,因此要在跑拓扑排序前先把这些点对结果的影响消除。注意到边权可能为负数,因此初始化存最长路的数组时注意不要初始化为 0。

#include<bits/stdc++.h>
#define endl '\n'

using namespace std;

const int maxn = 1e5+3;
struct edge
{
    int to,len;
};
vector<edge>p[maxn]; // 存边和边权
int f[maxn],ind[maxn]; // 存最长路和入度
queue<int>q;

void solve()
{
    int n,m; 
    cin>>n>>m;
    int u,v,w;
    for(int i=1;i<=m;i++)
    {
        cin>>u>>v>>w;
        p[u].push_back({v,w});
        ind[v]++;
    }
    for(int i=2;i<=n;i++)
    {
        f[i]=-1e9;
        if(!ind[i]) q.push(i);
    }
    // 将除 1 外入度为 0 的点的影响消除 
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=0;i<p[x].size();i++)
        {
            ind[p[x][i].to]--;
            if(!ind[p[x][i].to]) q.push(p[x][i].to);
        }
    }
    q.push(1);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=0;i<p[x].size();i++)
        {
            f[p[x][i].to]=max(f[p[x][i].to],f[x]+p[x][i].len);
            ind[p[x][i].to]--;
            if(!ind[p[x][i].to]) q.push(p[x][i].to);
        }
    }
    if(f[n]==-1e9) cout<<-1<<endl;
    else cout<<f[n]<<endl;
    return ;
}


int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T; T=1;
    while(T--) solve();
    return 0;
}

车站分级
由于每次停靠都是按顺序来的,因此每段停靠区间里没有出现过的车站一定比出现过的等级要低,依据此进行建图,用等级低的车站指向等级高的车站,然后拓扑排序每次删去所有入度为0的点,最后看拓扑排序由多少层即可。

#include<bits/stdc++.h>
#define endl '\n'

using namespace std;

const int maxn = 1e3+5;
bool pd[maxn]; // 记录每组数据的车站停靠情况
int a[maxn];
int p[maxn][maxn];
int in[maxn]; // 记录每个节点的入度
int ans;

void solve()
{
    int n,m;
    cin>>n>>m;
    while(m--)
    {
        memset(pd,0,sizeof(pd));
        int s; cin>>s;
        for(int i=1;i<=s;i++)
        {
            cin>>a[i];
            pd[a[i]]=true;
        }
        for(int i=a[1];i<=a[s];i++)
        {
            if(!pd[i])
            {
                for(int j=1;j<=s;j++)
                {
                    if(!p[i][a[j]])
                    {
                        p[i][a[j]]=1;
                        in[a[j]]++;
                    }
                }
            }
        }  
    }
    queue<int> q;
    // 将入度为 0 的点加入队列
    for(int i=1;i<=n;i++)
    {
        if(!in[i]) q.push(i);
    }
    // 拓扑排序 一次处理一层
    while(!q.empty())
    {
        int size=q.size();
        ans++;
        for(int i=0;i<size;i++)
        {
            int x=q.front();
            q.pop();
            for(int j=1;j<=n;j++)
            {
                if(p[x][j])
                {
                    in[j]--;
                    p[x][j]=0;
                    if(!in[j]) q.push(j);
                }
            }
        }
    }
    cout<<ans<<endl;
    return ;
}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int T; T=1;
    while(T--) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值