图论

dijkstra:

///朴素dijkstra算法 —— 模板题 AcWing 849. Dijkstra求最短路 I
///时间复杂是 O(n2+m)O(n2+m), nn 表示点数,mm 表示边数
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int n,m;
int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);//将每个点每个距离赋值为无穷大
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
int main()
{
    memset(g,0x3f,sizeof g);
    cin>>n>>m;
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=min(g[a][b],c);
    }
    int t=dijkstra();
    cout<<t<<endl;
}

 

堆优化版dijkstra:

///堆优化版dijkstra —— 模板题 AcWing 850. Dijkstra求最短路 II
///时间复杂度 O(mlogn)O(mlogn), nn 表示点数,mm 表示边数
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,res,dist[N],s;
int e[N],ne[N],h[N],w[N],idx;
typedef pair<int,int>pii;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool vis[N];
void dijkstra()
{
    memset(dist,0x3f3f3f,sizeof dist);
    dist[s]=0;
    priority_queue<pii,vector<pii>,greater<pii>>que;
    que.push({0,s});//这里的必须是{0,s},小根堆是对第一个的值进行排序,我们要根据距离排序
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int dis=now.first,head=now.second;
        if(vis[head]) continue; vis[head]=true;//不加的话时间复杂度会变高
        for(int i=h[head];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dis+w[i]){
                dist[j]=dis+w[i];
                que.push({dist[j],j});
            }
        }
    }
}
signed main()
{  
    memset(h,-1,sizeof h);
    cin>>n>>m>>s;
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    dijkstra();
    for(int i=1;i<=n;i++) cout<<dist[i]<<" ";
    return 0;
}

 

关于所有点到一个点的单源最短路(dijkstra):

其实反着建图就可以了

//https://www.luogu.com.cn/problem/P1342
//https://www.luogu.com.cn/problem/P1629
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
int n,m,e[N],ne[N],h[N],w[N],idx,dist[N],res=0;
int e1[N],ne1[N],h1[N],w1[N],idx1;
typedef pair<int,int>pii;
bool vis[N];
void add1(int a,int b, int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void add2(int a,int b,int c){
    e1[idx1]=b,w1[idx1]=c,ne1[idx1]=h1[a],h1[a]=idx1++;
}
void dijkstra1(int st){
    priority_queue<pii,vector<pii>,greater<pii>>que;
    for(int i=1;i<=n;i++) dist[i]=0x7f7f7f7f,vis[i]=false;
    dist[st]=0,que.push({dist[st],st});
    while(!que.empty()){
        auto now=que.top();que.pop();
        int y=now.first,x=now.second;
        if(vis[x]) continue; vis[x]=true;
        for(int i=h[x];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>y+w[i]){
                dist[j]=y+w[i];
                que.push({dist[j],j});
            }
        }
    }
}
void dijkstra2(int st){
    priority_queue<pii,vector<pii>,greater<pii>>que;
    for(int i=1;i<=n;i++) dist[i]=0x7f7f7f7f,vis[i]=false;
    dist[st]=0,que.push({dist[st],st});
    while(!que.empty()){
        auto now=que.top();que.pop();
        int y=now.first,x=now.second;
        if(vis[x]) continue; vis[x]=true;
        for(int i=h1[x];~i;i=ne1[i]){
            int j=e1[i];
            if(dist[j]>y+w1[i]){
                dist[j]=y+w1[i];
                que.push({dist[j],j});
            }
        }
    }
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    memset(h, -1, sizeof h);
    memset(h1, -1, sizeof h1);
    for(int i=1;i<=m;i++){
        int a,b,c; cin>>a>>b>>c;
        add1(a,b,c),add2(b,a,c);
    }
    dijkstra1(1);
    for(int i=2;i<=n;i++) res+=dist[i];
    dijkstra2(1);
    for(int i=2;i<=n;i++) res+=dist[i];
    cout<<res<<endl;
    return 0;
}

单源次短路: 

//https://www.luogu.com.cn/problem/P2149
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
vector<pair<int,int>>point(N);
vector<pair<int,double>>g[N];
vector<int>pre(N);
vector<double>dijkstra(int n,int aa,int bb){
    vector<double>dist(n+1,1e9);
    vector<bool>vis(n+1,false);
    priority_queue<pair<double,int>,vector<pair<double,int>>,greater<pair<double,int>>>que;
    que.push({0,1}),dist[1]=0;
    while(!que.empty()){
        auto now=que.top(); que.pop();
        int x=now.second;
        double y=now.first;
        if(vis[x]) continue; vis[x]=true;
        for(auto u:g[x]){
            int v=u.first;
            double w=u.second;
            if((x==aa&&v==bb)||(x==bb&&v==aa)) continue;
            if(dist[v]>y+w){
                dist[v]=y+w,que.push({dist[v],v});
                if(aa==-1) pre[v]=x;
            }
        }
    }
    return dist;
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,m,a,b; cin>>n>>m;
    cin>>a>>b;
    point[1]={a,b};
    for(int i=2;i<=n-1;i++){
        cin>>a>>b;
        point[i]={a,b};
    }
    vector<vector<double>>dis(n+1,vector<double>(n+1));
    cin>>a>>b,point[n]={a,b};
    auto get_distance=[](pair<int,int> a,pair<int,int> b){
        int x=a.first,y=a.second,xx=b.first,yy=b.second;
        return (double)(sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy)));
    };   
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            dis[i][j]=get_distance(point[i],point[j]);
    for(int i=1;i<=m;i++){
        cin>>a>>b;
        double c=dis[a][b];
        g[a].push_back({b,c}),g[b].push_back({a,c});
    }
    vector<double>ans=dijkstra(n,-1,-1);
    // for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<'\n';
    double res=1e9;
    for(int i=n;i!=1;i=pre[i]){
        int aa=i,bb=pre[i];
        ans=dijkstra(n,aa,bb);
        // for(int i=1;i<=n;i++) cout<<ans[i]<<' '; cout<<'\n';
        res=min(res,ans[n]);
    }
    if(res==1e9) cout<<"-1"<<'\n';
    else printf("%.2lf\n",res);
    return 0;
}

 

 

最短路计数,使用邻接表动态数组,bfs:
///最短路计数,使用邻接表动态数组,bfs
#include<bits/stdc++.h>
using namespace std;
const int N=1000010,mod=100003;
vector<int> g[N];
int dist[N];
bool vis[N];
int cnt[N];
int n,m;
void bfs()
{
    memset(dist,0x3f3f3f,sizeof dist);
    memset(vis,false,sizeof vis);
    queue<int>que;
    dist[1]=0;
    cnt[1]=1;
    que.push(1);
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(auto v:g[u])
        {
            if(!vis[v])
            {
                if(dist[v]>dist[u]+1)///如果此时不是1最短路,更新最短路
                {
                    dist[v]=dist[u]+1;
                    cnt[v]=cnt[u];
                    cnt[v]%=mod;
                    que.push(v);
                }
                else if(dist[v]==dist[u]+1)///如果走的路是最短路,就在这个地方加上1;
                {
                    cnt[v]+=cnt[u];
                    cnt[v]%=mod;
                }
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);///储存邻接表
        g[b].push_back(a);
    }
    bfs();
    for(int i=1;i<=n;i++)
        cout<<cnt[i]<<endl;
    return 0;
}

 

ballman-ford:
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int n,m,k;
int dist[N],backup[N];
struct node
{
    int a,b,w;
}edge[N];
int ballman_ford()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    for(int i=0;i<k;i++)
    {
        memcpy(backup,dist,sizeof dist);
        for(int j=0;j<m;j++)
        {
            int a=edge[j].a,b=edge[j].b,w=edge[j].w;
            dist[b]=min(dist[b],backup[a]+w);
        }
    }
    if(dist[n]>0x3f3f/2) return -1;
    else return dist[n];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        cin>>a>>b>>w;
        edge[i]={a,b,w};
    }
    int t =ballman_ford();
    if(t==-1)  puts("impossible");
    else cout<<t<<endl;
    return 0;
}

 

SPFA:

#include <bits/stdc++.h>
using namespace std;

const int N = 2030, M = N * 50;        //N为顶点数,M为边数

//数组构造邻接表, n为定点数,m为边数
int n, m
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
queue<int> q;
bool st[N];

// 添加一条边a->b,边权为c
void add(int a, int b, int c)
{
    //e[idx]指的是编号为idx的边的去向为何处,h[a]存储的是从a出发的所有边的单链表表头
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa()  // 求1号点到n号点的最短路距离
{
    memset(dist, 0x3f, sizeof dist);    //0x3f代表最大值,最小值可用0xcf
    dist[1] = 0;
    q.push(1);
    st[1] = true;

    while (q.size())        //while循环控制出队
    {
        int t = q.front();
        q.pop();
        st[t] = false;

        for (int i = h[t]; i != -1; i = ne[i])        //for循环控制入队+更新
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
            //  cnt[j] = cnt[t] + 1;             // 用于判断是否存在负环
            //  pre[i] = t;                         // 用于记录前驱节点(记录路径)
                if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
                {
                    q.push(j);
                    st[j] = true;
                }
            //  if(cnt[j] >= n) return true;    // 用于判断是否存在负环
            }

            // 最短路计数要区分dist[j] > 和 == 的情况,不能只讨论dist[j] > dist[t] + w[i]
            /**
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t]
            }
            else if (dist[j] == dist[t] + w[i]) {
                cnt[i] += cnt[t];    //最短路计数
            }
            if (!st[j])     // 如果队列中已存在j,则不需要将j重复插入
            {
                q.push(j);
                st[j] = true;
            }
            */
        }
    }
}

int main(){
    memset(h, -1, sizeof h);
    ...            //需先通过add函数,采取邻接表的方式构造好图
    spfa();
    ...         //输出路径权值和、判断是否有负环、输出最短路径...等操作

    /** 记录路径操作 */
    stack<int> help;    //遍历前驱节点,压入栈中,再取出来时可以变为正序的
    for (int i = n; ~i; i=pre[i]) help.push(i);    // n为路径终点
    while (help.size()) {
        printf("%d ", help.top());
        help.pop();
    }
}

 

///SPFA路径
///使用的邻接表储存
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int n,m;
int h[N],e[N],ne[N],w[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++;
}
int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    queue<int>que;
    que.push(1);
    st[1]=true;
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        st[now]=false;
        for(int i=h[now];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[now]+w[i])
            {
                dist[j]=dist[now]+w[i];
                if(!st[j])
                {
                    que.push(j);
                    st[j]=true;
                }
            }
        }
    }
    if(dist[n]>0x3f3f/2) return -1;
    else return dist[n];
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    int t=spfa();
    if(t==-1) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    return 0;
}

 

///二维数组存SPFA
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
struct node
{
    int b,w;
}e;
int n,m;
int dist[N];
bool st[N];
vector<node>g[N];
int spfa()
{
    memset(dist,0x3f3f,sizeof dist);
    dist[1]=0;
    queue<int>que;
    que.push(1);
    st[1]=true;///表示我们的这个点已经入队列了
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        st[now]=false;///队头出去了,那就是这个点不在这个队列里了标记false;
       for(int i=0;i<g[now].size();i++)
       {
           int v=g[now][i].b;
           if(dist[v]>dist[now]+g[now][i].w)
           {
               dist[v]=dist[now]+g[now][i].w;
               if(!st[v])///如果队列里面没有他,再加入队列;
               {
                   st[v]=true;
                   que.push(v);
               }
           }
       }
    }
    if(dist[n]>0x3f3f/2) return -1;
    else return dist[n];
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        int a;
        cin>>a>>e.b>>e.w;
        g[a].push_back(e);///存入数据
    }
    int t=spfa();
    if(t==-1) cout<<"impossible"<<endl;
    else cout<<t<<endl;
    return 0;
}

 

SPFA判断负环:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5;
int cnt[N*2],res,n,m,dist[N*2],t;
struct node
{
    int b,w;
}e;
bool vis[2*N];
vector<node>g[N*2];
bool spfa()
{
    queue<int>que;
    dist[1]=0,vis[1]=true;
    que.push(1);
    while(!que.empty()){
        int now=que.front();que.pop();
        vis[now]=false;//队头出去了,那就标记不在
        for(int i=0;i<g[now].size();i++){
            int v=g[now][i].b;
            if(dist[v]>dist[now]+g[now][i].w){
                dist[v]=dist[now]+g[now][i].w;
                cnt[v]=cnt[now]+1;//路的边数+1
                if(cnt[v]>=n) return false;//如果,我是说如果当前路的边数已经大于n了,那很明显存在负环;
                if(!vis[v]) vis[v]=true,que.push(v);
            }
        }
    }
    return true;
}
signed main()
{
    cin>>t;
    while(t--){
        memset(cnt,0,sizeof cnt);
        memset(vis,false,sizeof vis);
        memset(dist,0x3f,sizeof dist);
        for (int j=0;j<=N;j++) g[j].clear();
        cin>>n>>m;
        bool f=false;
        for(int i=0;i<m;i++){
            int a,b,c;
            cin>>a>>b>>c;
            if(a==1) f=true;
            if(c>=0){
                g[a].push_back({b,c});
                g[b].push_back({a,c});
            }
            else g[a].push_back({b,c});
        }
        if(!spfa()) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

 

floyd算法,适用于多个询问:
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,k;
int d[N][N];
void floyd()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int x=1;x<=n;x++)
            d[j][x]=min(d[j][x],d[j][i]+d[i][x]);
}
int main()
{
    cin>>n>>m>>k;
    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]=1e9;
    while(m--)
    {
        int a,b,w;
        cin>>a>>b>>w;
        d[a][b]=min(d[a][b],w);
    }
    floyd();
    while(k--)
    {
        int a,b;
        cin>>a>>b;
        if(d[a][b]>1e9/2) cout<<"impossible"<<endl;
        else cout<<d[a][b]<<endl;
    }
    return 0;
}

Floyd应用:

///floyd算法的应用
//https://www.luogu.com.cn/record/110497608
///floyd算法应用与求每个点到任意一点的最短距离,然后根据此算法就可以按照题目要求的路径算出最短路径来
///不需要建图,三重暴力循环解决战斗
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,num[100005],d[N][N],res;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>num[i];
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>d[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                d[j][k]=min(d[j][k],d[j][i]+d[i][k]);
    int st=1,ed=n;
    for(int i=1;i<=m;i++)
    {
        res+=d[st][num[i]];
        st=num[i];
    }
    res+=d[st][ed];
    cout<<res<<endl;
    return 0;
}
///floyd 应用2 变形
//https://www.luogu.com.cn/problem/B3611
#include<bits/stdc++.h>
using namespace std;
const int N=500;
int n,d[N][N],mp[N][N];
int main()
{
    cin>>n;;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) cin>>d[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                d[j][k]=max(d[j][k],min(d[j][i],d[i][k]));///1表示为连通,0表示为不连通,试想一下,如果d[j][k]和另一个有一个为1,那是不是就是可以连通?
                ///但是这里为什么第二个要取min呢,如果这两者有一个为0,说明,j和k之间没有中转站,此时如果j和k不是直接连的也就是,d[j][k]不是1,那么这两点不可能连上;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            cout<<d[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}
// [USACO08JAN] Cow Contest S
// floyd不仅能求出最短距离,还能代表当前点i可以走到点j
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3;
int n,m,res,g[N][N],num[N];
void floyd()
{  
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
signed main()
{
    cin>>n>>m;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++) g[i][j]=2e9;
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        g[a][b]=1;
    }
    floyd();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(g[i][j]<2e9/2&&i!=j) num[i]++,num[j]++;
    for(int i=1;i<=n;i++) if(num[i]>=n-1) res++;
    cout<<res;
    return 0;
}

//https://www.luogu.com.cn/problem/P1119
//从本质理解folyd算法,进行实时的更新,时间复杂度O(n*m*n+q)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,M=500;
int n,m,f[M][M],t[N],q,now;
int main(){
    cin>>n>>m;
    memset(f,0x3f,sizeof f);
    for(int i=0;i<n;i++) cin>>t[i],f[i][i]=0;
    while(m--){
        int a,b,c; cin>>a>>b>>c;
        f[a][b]=f[b][a]=c;
    }
    cin>>q;
    while(q--){
        int a,b,c; cin>>a>>b>>c;
        while(t[now]<=c&&now<n){
            for(int i=0;i<n;i++)
                for(int j=0;j<n;j++)
                    f[i][j]=min(f[i][j],f[i][now]+f[now][j]);
            now++;
        }    
        if(t[a]>c||t[b]>c||f[a][b]==0x3f3f3f3f) cout<<-1<<endl;
        else cout<<f[a][b]<<endl;
    }
}

 

分层图:

///分层图
//https://blog.csdn.net/qq_45735851/article/details/108219481?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168420864216800222869973%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168420864216800222869973&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-108219481-null-null.142^v87^control,239^v2^insert_chatgpt&utm_term=%E5%88%86%E5%B1%82%E5%9B%BE%E6%9C%80%E7%9F%AD%E8%B7%AF&spm=1018.2226.3001.4187
///分层图最经典的例题之一
///可以试想一下,咱们最短路是一个图,分层图的意思是创建多个图,然后每个图都不一样,遍历完之后找到最优解
///可以看出来分层图难在建图
#include<bits/stdc++.h>
using namespace std;
const int N=10000010;
int n,m,s,k,t,idx;
int e[N],ne[N],h[N],dist[N],w[N];
bool vis[N];
typedef pair<int,int>pii;
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra()///标准模板
{
    priority_queue<pii,vector<pii>,greater<pii>> que;
    memset(dist,0x3f3f3f,sizeof dist);
    dist[s]=0;
    que.push({0,s});
    while(!que.empty())
    {
        auto now=que.top();
        que.pop();
        int y=now.first,x=now.second;
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>y+w[i])
            {
                dist[j]=y+w[i];
                que.push({dist[j],j});
            }
        }
    }
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>k>>s>>t;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        //add(b,a,c);
        for(int j=1;j<=k;j++)///建图
        {
            add(j*n+a,j*n+b,c);///把一层建好
            //add(j*n+b,j*n+a,c);
            add((j-1)*n+a,j*n+b,0);///把上一层建好,代表从上到下走0的距离;
           // add((j-1)*n+b,j*n+a,0);
        }
    }
    for(int i=0;i<k;i++) add(i*n+t,(i+1)*n+t,0);///有可能答案不需要非要用完k次免费次数,所以这里把所有终点都用距离为0连起来;
    dijkstra();
    int minn=0x3f;
    for(int i=0;i<=k;i++) minn=min(minn,dist[i*n+t]);///遍历所有终点,有可能没必要用完k次;
    cout<<minn;
    return 0;
}

差分约束: 

 

//差分约束算法

// 差分约束
// (1)求不等式组的可行解
// 源点需要满足的条件: 从源点出发,一定可以走到所有的边。步骤:
// [1] 先将每个不等式 xi <= xj + ck, 转化成一条从xj走到xi,长度为ck的一条边
// [2] 找一个超级源点,使得该源点一定可以遍历到所有边
// [3] 从源点求一遍单源最短路
// 结果1: 如果存在负环,则原不等式组一定无解
// 结果2: 如果没有负环,则dist[i]就是原不等式组的一个可行解
// (2) 如何求最大值或者最小值,这里的最值指的是每个变量的最值
// 结论: 如果求的是最小值,则应该求最长路;如果求的是最大值,则应该求最短路
// 问题: 如何转化xi < = c,其中c是一个常数,这类的不等式
// 方法: 建立一个超级源点,0,然后建立0->i,长度是c的边即可。以求xi的最大值为例: 
//      求所有从xi出发,构成的不等式链xi <= xj + c1 <= xk + c2 + c1 <= ... <= x0+c1+c2+...所计算出的上界
//      最终xi的最大值等于所有上界的最小值。


//【模板】差分约束算法
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,dist[N],cnt[N],s,res;
int e[N],ne[N],h[N],w[N],idx;
bool vis[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    queue<int>que;
    que.push(0),dist[0]=0,vis[0]=true;
    while(!que.empty()){
        int now=que.front(); que.pop();
        vis[now]=false,s++;
        for(int i=h[now];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[now]+w[i]){
                dist[j]=dist[now]+w[i];
                cnt[j]=cnt[now]+1;
                if(cnt[now]>=n+1||s>1e6) return false;
                if(!vis[j]) vis[j]=true,que.push(j);
            }
        }
    }
    return true;
}
signed  main()
{
    memset(h,-1,sizeof h);
    memset(dist,0x3f,sizeof dist);
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(b,a,c);//切记,是从b向a建立一条边
        //因为我们是从查询的点出发,然后一直查询到0,所以是从j->i的边
    }
    for(int i=n;i;i--) add(0,i,1);
    if(!spfa()) cout<<"NO"<<endl;
    else for(int i=1;i<=n;i++) cout<<dist[i]<<" ";
    return 0;

 

差分约束应用:

//糖果
//https://www.luogu.com.cn/problem/P3275

//队列版
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=5e5;
int n,m,res,dist[N],cnt[N],s;
int e[N],ne[N],w[N],h[N],idx;
bool vis[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    memset(dist,-0x3f,sizeof dist);
    queue<int>que;
    que.push(0),dist[0]=0,vis[0]=true;//从0开始
    while(!que.empty()){
        int now=que.front(); que.pop();
        vis[now]=false,s++;
        for(int i=h[now];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]<dist[now]+w[i]){
                dist[j]=dist[now]+w[i];
                cnt[j]=cnt[now]+1;
                if(cnt[j]>=n+1||s>1e6) return false;//因为从0开始,所以是n+1次
                if(!vis[j]) vis[j]=true,que.push(j);
            }
        }
    }
    return true;
}
signed main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>c>>a>>b;
        if(c==1) add(b,a,0),add(a,b,0);
        else if(c==2) add(a,b,1);
        else if(c==3) add(b,a,0);
        else if(c==4) add(b,a,1);
        else add(a,b,0);
    }
    for(int i=n;i;i--) add(0,i,1);//将所有的点从0点连通
    if(!spfa()) cout<<-1<<endl;
    else{
        for(int i=1;i<=n;i++) res+=dist[i];
        cout<<res<<endl;
    }
    return 0;
}
//栈版(有时候会比队列快)

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=5e5;
int n,m,res,dist[N],cnt[N],s;
int e[N],ne[N],w[N],h[N],idx;
bool vis[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    memset(dist,-0x3f,sizeof dist);
    stack<int>que;
    que.push(0),dist[0]=0,vis[0]=true;//从0开始
    while(!que.empty()){
        int now=que.top(); 
        que.pop(),vis[now]=false,s++;
        for(int i=h[now];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]<dist[now]+w[i]){
                dist[j]=dist[now]+w[i];
                cnt[j]=cnt[now]+1;
                if(cnt[j]>=n+1||s>1e6) return false;//因为从0开始,所以是n+1次
                if(!vis[j]) vis[j]=true,que.push(j);
            }
        }
    }
    return true;
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>c>>a>>b;
        if(c==1) add(b,a,0),add(a,b,0);
        else if(c==2) add(a,b,1);
        else if(c==3) add(b,a,0);
        else if(c==4) add(b,a,1);
        else add(a,b,0);
    }
    for(int i=n;i;i--) add(0,i,1);
    if(!spfa()) cout<<-1<<endl;
    else{
        for(int i=1;i<=n;i++) res+=dist[i];
        cout<<res<<endl;
    }
    return 0;
}

 

 最小生成树:

//最小生成树

//朴素prim:
//把所有距离初始化为正无穷,然后每循环一次寻找距离集合最近的距离,并更新

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+10;
int n,m,dist[N],g[N][N],res;
bool vis[N];
int prim()
{
    dist[1]=0;
    for(int i=0;i<n;i++){
        int t=-1;
        for(int j=1;j<=n;j++)//寻找最短距离的点
            if(!vis[j]&&(t==-1||dist[t]>dist[j])) t=j;
        if(i){
            if(dist[t]=0x3f3f3f3f) return 0x3f3f3f3f;
            res+=dist[t];
        }
        for(int j=1;j<=n;j++) dist[j]=min(dist[j],g[t][j]);//更新最短距离
        vis[t]=true;
    }
    return res;
}
signed main()
{
    cin>>n>>m;
    memset(dist,0x3f,sizeof dist);
    memset(g,0x3f,sizeof g);
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    res=prim();
    if(res>=0x3f3f3f3f/2) cout<<"orz"<<endl;
    else cout<<res<<endl;   
    return 0;
}

//kruskal(速度快)

//将所有的边按权重进行快速排序
//枚举每条边,如果a,b不连通,就加到集合去

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=5010;
int p[N],n,m,res,cnt;
struct node
{
    int a,b,w;
    bool operator<(const node&W)const{
        return w<W.w;
    }
}edge[N];
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
signed main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int a,b,w;
        cin>>a>>b>>w;
        edge[i]={a,b,w};
    }
    sort(edge,edge+m);
    for(int i=0;i<m;i++){
        int a=edge[i].a,b=edge[i].b,w=edge[i].w;
        if(find(a)!=find(b)) p[find(a)]=b,res+=w,cnt++;
    }
    if(cnt<n-1) cout<<"orz"<<endl;
    else cout<<res<<endl;
    return 0;
}

 

最小生成树的应用:

//https://www.luogu.com.cn/problem/P1194
//买礼物

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,res,p[N],cnt;
struct node
{
    int a,b,w;
    bool operator<(const node&W)const
    {
        return w<W.w;
    }
}edge[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
signed main()
{
    cin>>m>>n;
    for(int i=0;i<=n;i++){    //看懂题意,开局先存边
        edge[cnt++]={0,i,m};
    }
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++){
            int a; cin>>a;
            if(a) edge[cnt++]={i,j,a};
        }
    sort(edge,edge+cnt);
    for(int i=0;i<=cnt;i++) p[i]=i;
    for(int i=0;i<cnt;i++){
        int a=edge[i].a,b=edge[i].b;
        if(find(a)!=find(b)) res+=edge[i].w,p[find(a)]=find(b);
    }
    cout<<res<<endl;
    return 0;
}

//https://www.luogu.com.cn/problem/P1195
//口袋的天空

//只需要将代价前n-k小的合成一块就行,比如有10个,需要合成7个
//只需要让6个是独自一个,其他四个合成1个就ok
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,res,cnt,p[N],k,num;
struct node
{
    int a,b,w;
    bool operator<(const node&W)const{
        return w<W.w;
    }
}q[N];
int find(int x){
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
signed main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        q[i]={a,b,c};
    }
    sort(q,q+m);
    for(int i=0;i<=n;i++) p[i]=i;
    for(int i=0;i<m;i++){
        int a=q[i].a,b=q[i].b;
        if(find(a)!=find(b)){
            cnt++;
            p[find(a)]=find(b);
            res+=q[i].w;
            if(cnt>=n-k) break;
        }
    }
    if(cnt<k-1) cout<<"No Answer"<<endl;
    else cout<<res<<endl;
    return 0;
}

//https://www.luogu.com.cn/problem/P1396
//营救

//正难则反,速成最大生成树?
//考虑把所有的桥都拆了,然后重新修建
//为什么是从大到小排序?
//划分到不同连通块,实际上相当于把一个关键点与其他非关键点连通,并且不与其他关键点联通,也就把删边转化为了加边。
//既然是加边,求得是最小的摧毁代价,那么我们就求最大的修建代价,然后用总的减去就可以了,所以要从大到小
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+10;
int n,m,res,p[N],e;
bool vis[N];
struct node
{
    int a,b,w;
    bool operator<(const node&W) const
    {
        return w>W.w;
    }
}q[N];
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
signed main()
{
    cin>>n>>m;
    for(int i=0;i<m;i++) cin>>e,vis[e]=true;//标记敌人
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        q[i]={a,b,c},res+=c;
    }
    sort(q+1,q+n);//从大到小排序,先修代价大的
    for(int i=0;i<=n;i++) p[i]=i;
    for(int i=1;i<n;i++){
        int a=find(q[i].a),b=find(q[i].b);
        if(vis[a]&&vis[b]) continue;//注意,这里不能修两个敌人节点的桥
        p[a]=b,res-=q[i].w,vis[a]=vis[b]=vis[a]||vis[b];//如果一个正常节点连接上了敌人节点,那么这个正常节点也会变为敌人
        //如果不标记的话,就会可以通过i->j->k,j这个间接节点到达,达不到孤立的效果
    }
    cout<<res;
    return 0;
}

 

最近公共祖先(LCA):

(1)倍增:

//最近公共祖先

// (1) 向上标记法 O(n)
// (2) 倍增
// fa[i, j] 表示从i开始,向上走2^j步所能走到的节点。0 <= j <= logn,depth[i] 表示深度
// 哨兵: 如果从i开始跳2^j步会跳过根节点,那么fa[i,j] = 0。depth[O] = 0
// 步骤:
// [1] 先将两个点跳到同一层
// [2] 让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。
// 预处理 O(nlogn)
// 查询 O(logn)

//http://ybt.ssoier.cn:8088/problem_show.php?pid=1557
//孙祖关系

#include<bits/stdc++.h>
using namespace std;
const int N=40010;
int n,m,res,depth[N],fa[N][16];
int e[N*2],ne[N*2],h[N],idx,root;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs(int root)
{
    memset(depth,0x3f,sizeof depth);
    depth[0]=0,depth[root]=1;
    queue<int>que;
    que.push(root);
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];i!=-1;i=ne[i]){
            int j=e[i];
            if(depth[j]>depth[now]+1){
                depth[j]=depth[now]+1,fa[j][0]=now,que.push(j);
                for(int k=1;k<=15;k++)
                    fa[j][k]=fa[fa[j][k-1]][k-1];
            }
        }
    }
}
int lca(int a,int b)
{
    if(depth[a]<depth[b]) swap(a,b);
    for(int k=15;k>=0;k--){
        if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];
    }
    if(a==b) return a;
    for(int k=15;k>=0;k--)
        if(fa[a][k]!=fa[b][k]) a=fa[a][k],b=fa[b][k];
    return fa[a][0];
}
int main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=0;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(b==-1) root=a;
        else add(a,b),add(b,a);
    }
    bfs(root);
    cin>>m;
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        int p=lca(a,b);
        if(p==a) puts("1");
        else if(p==b) puts("2");
        else puts("0");
    }
    return 0;
}

 

//https://www.luogu.com.cn/problem/P3884
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int e[N],ne[N],idx,h[N],num[N],dist[N];
int fa[N][16],n,m,res,ans,dis,st,ed;
int dep,wid;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs()
{
    memset(dist,0x3f,sizeof dist);
    queue<int>que;
    que.push(1),dist[1]=1,dist[0]=0;
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[now]+1){
                dist[j]=dist[now]+1;
                dep=max(dep,dist[j]);
                fa[j][0]=now,que.push(j);
                for(int k=1;k<=15;k++)
                    fa[j][k]=fa[fa[j][k-1]][k-1];
            }
        }
    }
}
int lca(int a,int b)
{
    if(dist[a]<dist[b]) swap(a,b);
    for(int k=15;k>=0;k--)
        if(dist[fa[a][k]]>=dist[b]) a=fa[a][k];
    if(a==b) return a;
    for(int k=15;k>=0;k--)
        if(fa[a][k]!=fa[b][k]) a=fa[a][k],b=fa[b][k];
    return fa[a][0];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v; cin>>u>>v;
        add(u,v);
    }
    bfs();
    for(int i=1;i<=n;i++){
        num[dist[i]]++;
        if(wid<num[dist[i]]) wid=num[dist[i]];
    }
    cin>>st>>ed;
    cout<<dep<<endl<<wid<<endl<<(dist[st]-dist[lca(st,ed)])*2+dist[ed]-dist[lca(st,ed)];
    return 0;
}

 

 

 

(2)Tarjan:

// (3) Tarjan一离线求LCA O(n + m)
//     在深度优先遍历时,将所有点分成三大类
//     [1] 已经遍历过,且回溯过的点
//     [2] 正在搜索的分支
//     [3] 还未搜索到的点

//http://ybt.ssoier.cn:8088/problem_show.php?pid=1552
//点的距离

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,res[N],p[N],dist[N];
int e[N],ne[N],w[N],h[N],idx;
typedef pair<int,int>pii;
vector<pii>query[N];
int vis[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int find(int x)
{
    if(x!=p[x]) p[x]=find(p[x]);
    return p[x];
}
void dfs(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dist[j]=dist[u]+1;
        dfs(j,u);
    }
}
void tarjan(int u)
{
    vis[u]=1;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!vis[j]) tarjan(j),p[j]=u;
    }
    for(auto i:query[u]){
        int x=i.first,id=i.second;
        if(vis[x]==2){
            int anc=find(x);
            res[id]=dist[u]+dist[x]-2*dist[anc];
        }
    }
    vis[u]=2;
}
signed main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    cin>>m;
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        if(a!=b) query[a].push_back({b,i}),query[b].push_back({a,i});
    }
    for(int i=0;i<=n;i++) p[i]=i;
    dfs(1,-1);tarjan(1);
    for(int i=0;i<m;i++) printf("%d\n",res[i]);
    return 0;
}

 (3) 树剖LCA

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
vector<int>g[N];
int top[N],fa[N],dep[N],son[N],sz[N];
void dfs1(int u){
    sz[u]=1,dep[u]=dep[fa[u]]+1;
    for(auto x:g[u]){
        if(x==fa[u]) continue;
        fa[x]=u;
        dfs1(x);
        sz[u]+=sz[x];
        if(sz[son[u]]<sz[x]) son[u]=x;
    }
}
void dfs2(int u,int h){
    top[u]=h;
    if(son[u]) dfs2(son[u],h);
    for(auto x:g[u]){
        if(x==fa[u]||x==son[u]) continue;
        dfs2(x,x);
    }
}
int lca(int a,int b){
    while(top[a]!=top[b]){
        if(dep[top[a]]>dep[top[b]]) a=fa[top[a]];
        else b=fa[top[b]];
    }
    return dep[b]>dep[a]?a:b;
}
signed main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,m,s; cin>>n>>m>>s;
    for(int i=1;i<n;i++){
        int a,b; cin>>a>>b;
        g[a].push_back(b),g[b].push_back(a);
    }
    dfs1(s),dfs2(s,s);
    while(m--){
        int a,b; cin>>a>>b;
        cout<<lca(a,b)<<endl;
    }
}

 

拓扑排序:

//拓扑排序

//拓扑排序=有向无环图

//家谱树
//https://www.luogu.com.cn/problem/B3644

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000;
int n,m,res,a[N],p[N],top[N];
int e[N],ne[N],h[N],idx;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
    queue<int>que;
    for(int i=1;i<=n;i++) if(p[i]==0) top[res++]=i,que.push(i);
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];~i;i=ne[i]){
            int j=e[i];
            p[j]--;
            if(p[j]==0) top[res++]=j,que.push(j);
        }
    }   
}
signed main()
{
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++)
        while(cin>>m&&m!=0) add(i,m),p[m]++;
    topsort();
    for(int i=0;i<res;i++) cout<<top[i]<<" ";
    return 0;
}

//奖金
//http://ybt.ssoier.cn:8088/problem_show.php?pid=1352

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,res,num,p[N],top[N];
int e[N],ne[N],h[N],idx;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
    queue<int>que;
    for(int i=1;i<=n;i++)
        if(!p[i]) top[i]=100,que.push(i);
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];~i;i=ne[i]){
            int j=e[i];
            p[j]--,top[j]=top[now]+1;
            if(p[j]==0) que.push(j);
        }
    }
}
signed main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        add(b,a),p[a]++;
    }
    topsort();
    for(int i=1;i<=n;i++){
        if(top[i]==0) return cout<<"Poor Xed",0;
        res+=top[i];
    }
    cout<<res;
    return 0;
}
//(2)差分约束算法
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,res,top[N],cnt[N],s;
int e[N],ne[N],h[N],idx,w[N];
bool vis[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    queue<int>que;
    top[0]=99;
    que.push(0),vis[0]=true;
    while(!que.empty()){
        int now=que.front(); que.pop();
        vis[now]=false; s++;
        for(int i=h[now];i!=-1;i=ne[i]){
            int j=e[i];
            if(top[j]<top[now]+1){
                top[j]=top[now]+1;
                cnt[j]=cnt[now]+1;
                if(cnt[j]>=n+1) return false;
                if(!vis[j]) vis[j]=true,que.push(j);
            }
        }
    }
    return true;
}
signed main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    memset(top,-0x3f,sizeof top);
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        add(b,a,1);
    }
    for(int i=n;i>=1;i--) add(0,i,1);
    if(!spfa()) cout<<"Poor Xed";
    else{
        for(int i=1;i<=n;i++) res+=top[i];
        cout<<res;
    }
    return 0;
}

//车站分级
//https://www.luogu.com.cn/problem/P1983

//拓扑排序
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,M=1e4+10;
int n,m,res,p[N],top[N],k,s[N];
int e[N],ne[N],h[N],idx,num;
bool vis[N],mp[M][M];//mp数组要开小,防止mle
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
    queue<int>que;
    for(int i=1;i<=n;i++) if(!p[i]) que.push(i);
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];~i;i=ne[i]){
            int j=e[i];
            p[j]--;
            if(p[j]==0){
                top[j]=top[now]+1;//如果没有比他小的了,他就是最小的,此时级别+1
                que.push(j);
            }
        }
    }
}
signed main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++){   //建图
        cin>>k;
        int st,ed;
        memset(vis,false,sizeof vis);
        for(int j=0;j<k;j++){
            cin>>s[j]; vis[s[j]]=true;
            if(j==0) st=s[j];
            if(j==k-1) ed=s[j]; 
        }
        for(int j=st;j<=ed;j++){
            if(!vis[j]){
                for(int e=0;e<k;e++)//利用mp数组防止重边爆RE
                    if(!mp[s[e]][j]) mp[s[e]][j]=true,p[j]++,add(s[e],j);
            }
        }
    }
    topsort();
    for(int i=1;i<=n;i++) res=max(res,top[i]);
    cout<<res+1<<endl; //最大的一级也算一个
    return 0;
}

拓扑排序的典型应用:

//Nastya and Potions
//https://codeforces.com/problemset/problem/1851/E
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,res,p[N],money[N],ans[N],t,a[N],x;
int e[N],ne[N],h[N],idx;
bool vis[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
    queue<int>que;
    for(int i=1;i<=n;i++){
        if(!p[i]) que.push(i);
        if(a[i]) ans[i]=a[i];
    }
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=h[now];~i;i=ne[i]){
            int j=e[i];
            p[j]--,money[j]+=ans[now];
            if(p[j]==0){
                que.push(j);
                ans[j]=min(money[j],ans[j]);
            }
        }
    }
}
void solve()
{
    cin>>n>>m;
    idx=0;
    for(int i=0;i<=n;i++) ans[i]=0,h[i]=-1,money[i]=0;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=0;i<m;i++) cin>>x,a[x]=0;
    for(int i=1;i<=n;i++){
        int j,y;
        cin>>j;
        while(j--) cin>>y,add(y,i),p[i]++;//一定要注意怎么建边
    }
    topsort();
    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
    cout<<endl;
}
signed main()
{
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

 

Tarjan(有向图联通缩点):

//有向图的强连通分量

//将有向环图变成有向无环图(DAG)

// Tarjan算法求强连通分量(SCC)
// 对每个点定义两个时间戳
// dfn[u]表示遍历到u的时间戳
// low[u]从u开始走,所能遍历到的最小时间戳是什么。
// u是其所在的强联通分量的最高点等价于dfn[u] == low[u]

//tarjan算法模板 O(n+m)
//由于当我们每次搜到最低点的时候,那个点一定是在整个连通分量里最小的点,此时把他加入数组
//依次循环我们会发现,数组从1-num是递增的顺序,也就是有序的DAG

void tarjan(int u)
{
    dfn[u]=low[u]=++times;
    _stack[++top]=u,vis[u]=true;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]);
        else if(vis[j]) low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u]){
        int y;
        ++scc_num;
        do{
            y=stk[top--];
            vis[y]=false,id[y]=scc_num,scc[scc_num].push_back(y);
        }while(y!=u)
    }
}

 

Tarjan应用:

//受欢迎的牛
//https://www.luogu.com.cn/problem/P2341
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=1e5+10;
int n,m,res,p[N],size[N],id[N];
int e[N],ne[N],h[N],idx,num;
int _stack[N],dfn[N],low[N],_size[N];
int top,times,scc_num;
bool vis[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++times;
    _stack[++top]=u,vis[u]=true;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]);
        else if(vis[j]) low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        ++scc_num;
        do{
            y=_stack[top--];
            vis[y]=false,id[y]=scc_num,_size[scc_num]++;
        }while(y!=u);
    }
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);//缩点
    for(int i=1;i<=n;i++)
        for(int j=h[i];~j;j=ne[j]){
            int k=e[j],a=id[i],b=id[k];
            if(a!=b) p[a]++;//统计出度
        }
    for(int i=1;i<=scc_num;i++)
        if(!p[i]){
            res+=_size[i],num++;
            if(num>1) return cout<<"0"<<endl,0;
        }
    cout<<res<<endl;
    return 0;
}

 

// //缩点
// //https://www.luogu.com.cn/problem/P3387
//本题基本思想,将连通图用tarhan缩成DAG图然后跑一遍最长路就可以
//下面有几个注意点
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,res,p[N],dist[N],a[N];
int e[N],ne[N],h[N],idx,money[N];
//int _e[N],_ne[N],_h[N],_idx;
int dfn[N],low[N],_stack[N],id[N];
int scc_num,times,top;
vector<int>mp[N]; 
bool vis[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
    low[u]=dfn[u]=++times;
    _stack[++top]=u,vis[u]=true;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]);
        else if(vis[j]) low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        ++scc_num;
        do{
            y=_stack[top--];
            vis[y]=false,money[scc_num]+=a[y],id[y]=scc_num; //链表都是新得了,我们得到的DAG是图是全新的
            //所以此时的变量时SCC_NUM,我们要开一个新的数组记录边权值,这里用money
        }while(y!=u);
    }
}
void topsort()
{
    queue<int>que;
    for(int i=1;i<=scc_num;i++) if(!p[i]) que.push(i),dist[i]=money[i];
    while(!que.empty()){
        int now=que.front(); que.pop();
        for(int i=0;i<mp[now].size();i++){
            int j=mp[now][i];
            p[j]--,dist[j]=max(dist[j],dist[now]+money[j]);
            if(p[j]==0) que.push(j);
        }
    } 
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        add(x,y);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
        for(int j=h[i];~j;j=ne[j]){
            int k=e[j];
            int x=id[i],y=id[k];//这里不是id[j],而是id[i],因为我们要求的是第i个点与其所有邻点是否相通
            if(x!=y) mp[x].push_back(y),p[y]++; //注意点一,一定要重新再建一个链表,而且之前的那个链表不能重置
        }
    topsort();
    for(int i=1;i<=n;i++) res=max(res,dist[i]);
    cout<<res<<endl;
    return 0;
}

 

//[ZJOI2007] 最大半连通子图
//https://www.luogu.com.cn/problem/P2272
#include<bits/stdc++.h>
#define int long long
#include<unordered_set>
using namespace std;
const int N=1e5+10,M=2e6+10;
int n,m,res,dist[N],p[N],cnt[N],mod,a[N];
int e[M],ne[M],h[N],hs[N],idx,ans;
int _stack[N],dfn[N],low[N],id[N];
int top,times,scc_num;
bool vis[N];
unordered_set<int>s;

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

void tarjan(int u)
{
    low[u]=dfn[u]=++times;
    vis[u]=true,_stack[++top]=u;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]) tarjan(j),low[u]=min(low[u],low[j]);
        else if(vis[j]) low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        ++scc_num;
        do{
            y=_stack[top--];
            vis[y]=false,id[y]=scc_num,a[scc_num]++;
        }while(y!=u);
    }
}

// (1) void topsort()
// {
//     queue<int>que;
//     for(int i=1;i<=scc_num;i++) if(!p[i]) que.push(i),dist[i]=a[i],cnt[i]=1;
//     while(!que.empty()){
//         int now=que.front(); que.pop();
//         for(int i=hs[now];~i;i=ne[i]){
//             int j=e[i];
//             if(dist[j]<dist[now]+a[j]){
//                 dist[j]=dist[now]+a[j];
//                 cnt[j]=cnt[now]%mod;
//             }
//             else if(dist[j]==dist[now]+a[j]) cnt[j]=(cnt[j]+cnt[now])%mod;
//             p[j]--;
//             if(p[j]==0) que.push(j);
//         }
//     }
// }

// int dfs(int u)
// {
//     if(dist[u]) return dist[u];
//     for(int i=0;i<g[u].size();i++){
//         int x=dfs(i);
//         if(dist[u]<x+a[i]){
//             dist[u]=x+a[i];
//             cnt[u]=cnt[i]%mod;
//         }
//         else if(dist[u]==x+a[i]) cnt[u]=(cnt[u]+cnt[i])%mod;
//     }
//     return dist[u];
// }

signed main()
{
    cin>>n>>m>>mod;
    memset(h,-1,sizeof h);
    memset(hs,-1,sizeof hs);
    while(m--){
        int a,b;
        scanf("%lld%lld",&a,&b);
        add(h,a,b);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
        for(int j=h[i];~j;j=ne[j]){
            int k=e[j],x=id[i],y=id[k];
            int hash=x*M+y;
            if(x!=y&&!s.count(hash)) p[y]++,add(hs,x,y),s.insert(hash);
        }
//    topsort();
//   (2) spfa();//参考tarjan一章中,一样的道理跑一遍最长路就ok了
//(3) 由于缩点之后已经成为逆着的拓扑序了,所以直接倒着推一遍就可以了
    // for(int i=scc_num;i;i--){
    //     if(!dist[i]) dist[i]=a[i],cnt[i]=1;
    //     for(int j=hs[i];~j;j=ne[j]){
    //         int k=e[j];
    //         if(dist[k]<dist[i]+a[k]){
    //             dist[k]=dist[i]+a[k];
    //             cnt[k]=cnt[i];
    //         }
    //         else if(dist[k]==dist[i]+a[k]) cnt[k]=(cnt[k]+cnt[i])%mod;
    //     }
    // }

    for(int i=1;i<=scc_num;i++){
        if(res<dist[i]) res=dist[i],ans=cnt[i];
        else if(res==dist[i]) ans=(ans+cnt[i])%mod;
    }
    cout<<res<<endl<<ans;
    return 0;
}

 Tarjan求无向图连通(边连通,求桥(割边)):

//无向图的连通

//边的双连通

//Redundant Paths G
//https://www.luogu.com.cn/problem/P2860
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,res,p[N];
int e[N],ne[N],h[N],idx;
int dfn[N],low[N],_stack[N],id[N];
int top,dcc_num,times;
bool vis[N],bridge[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u, int from) {
    low[u] = dfn[u] = ++times;  // 设置节点u的深度和最低深度
    _stack[++top] = u;          // 将节点u入栈

    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];  // j为当前遍历到的相邻节点
        if (!dfn[j]) {  // 如果相邻节点j还没有被访问过
            tarjan(j, i);  // 递归访问节点j
            low[u] = min(low[u], low[j]);  // 更新节点u的最低深度
            if (dfn[u] < low[j])  // 如果从节点j能够到达更深的地方
                bridge[i] = bridge[i ^ 1] = true;  // 标记边i为桥
        } else if (i != (from ^ 1)) { //from^1是上一条边的反向边,也就是从父节点到当前节点的边
            low[u] = min(low[u], dfn[j]);  // 更新节点u的最低深度
        }
    }

    if (low[u] == dfn[u]) {
        int y;
        ++dcc_num;  // 新的双连通分量编号
        do {
            y = _stack[top--];  // 从栈中弹出节点,构成一个新的双连通分量
            id[y] = dcc_num;    // 标记节点属于第dcc_num个双连通分量
        } while (y != u);
    }
}
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),add(b,a);
    }
    tarjan(1,-1);
    for(int i=0;i<idx;i++)
        if(bridge[i]) p[id[e[i]]]++;
    for(int i=1;i<=dcc_num;i++) if(p[i]==1) res++:
    cout<<(res+1)/2;//记住,无方向图变成双连通分量所需要建的桥的数量为 res/2向上取整
    return 0;
}
//炸铁路
//https://www.luogu.com.cn/problem/P1656
//求桥的数量就可以
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,res,p[N];
int e[N],ne[N],idx,h[N];
int _stack[N],dfn[N],low[N],id[N];
int top,times,dcc_num;
bool vis[N];
vector<pair<int,int>>g;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool cmp(pair<int,int>a,pair<int,int>b)
{
    if(a.first==b.first) return a.second<b.second;
    else return a.first<b.first;
}
void tarjan(int u,int from)
{
    low[u]=dfn[u]=++times;
    _stack[++top]=u;
    for(int i=h[u];~i;i=ne[i]){
        int j=e[i];
        if(!dfn[j]){
            tarjan(j,i);
            low[u]=min(low[u],low[j]);
            if(dfn[u]<low[j]) vis[i]=vis[i^1]=true;
        }
        else if(i!=(from^1)) low[u]=min(low[u],dfn[j]);
    }
    if(low[u]==dfn[u]){
        int y;
        ++dcc_num;
        do{
            y=_stack[top--];
            id[y]=dcc_num;
        }while(y!=u);
    }
}
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),add(b,a);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0);
    for(int i=0;i<idx;i++) if(vis[i]) g.push_back({min(e[i],e[i^1]),max(e[i],e[i^1])}),vis[i]=vis[i^1]=false;
    sort(g.begin(),g.end(),cmp);
    for(auto i:g) cout<<i.first<<" "<<i.second<<endl;
    return 0;
}

 

posted @ 2023-05-24 13:03  o-Sakurajimamai-o  阅读(29)  评论(0编辑  收藏  举报
-- --