「Note」图论方向 - 图论基础

发布时间 2023-08-18 21:07:44作者: Eon_Sky

1. 差分约束

1.1. 介绍

差分约束算法用于解决如下问题:给出若干形如 \(x_a-x_b\le c\) (均为整数,可以为负数)的不等式,求一组解 \(\{x_i\}\),若不存在解则判断无解。

考虑将原式变形,变为 \(x_a\le x_b+c\)。观察到这与单源最短路里的三角形不等式 \(dis_a\le dis_b+w\)\(w\) 为节点 \(a,b\) 之间的某边边权)相似。我们使 \(x_i\) 为从源点到节点 \(i\) 的最短路径,对于不等式 \(x_a\le x_b+c\) 我们从节点 \(b\) 向节点 \(a\) 连一条边权为 \(c\)有向边。特殊地,我们建立一个源点 \(0\),从源点向所有节点连一条边权为 \(0\)有向边,并以源点作为最短路起点。

我们一般采用 SPFA 进行最短路,若图中存在负环,则差分约束系统无解。
特殊地,有些题也可转化为最长路形式进行拓扑排序,因题而异。

1.2. 常见技巧

1.2.1 常见变形

原式 变形 建边
\(x_a-x_b\le c\) \(x_a\le x_b+c\) add(b,a,c)
\(x_a-x_b\ge c\) \(x_b\le x_a-c\) add(a,b,-c)
\(x_a-x_b<c\) \(x_a\le x_b+c-1\) add(b,a,c-1)
\(x_a-x_b>c\) \(x_b\le x_a-c-1\) add(a,b,-c-1)
\(x_a=x_b\) \(x_a-x_b\le0,x_b-x_a\le0\) add(a,b,0),add(b,a,0)

1.2.2 简单性质

显著的,将我们得出的一组解 \(x_i\) 整体加减一个常量不影响正确性,因为都消掉了。

1.3 题目

\(\color{limegreen}{P5960\ 【模板】差分约束算法}\)

模板题。

\(\text{Code:}\)

#define LL long long
#define UN unsigned
#include<bits/stdc++.h>
using namespace std;
//--------------------//
const int N=1e5+2,M=1e5+2;

int n,m;
//----------//
//Edge
struct Edge
{
    int to,w,nex;
}edge[M];
int tot,head[N];
void add(int from,int to,int w)
{
    edge[++tot].nex=head[from];
    head[from]=tot;
    edge[tot].to=to;
    edge[tot].w=w;
    return;
}
//--------------------//
//SPFA
bool vq[N];
int v[N],dis[N];
queue<int>q;
void SPFA(int st)
{
    //printf("ST:%d\n",st);
    memset(v,0,sizeof(v));
    memset(vq,0,sizeof(vq));
    memset(dis,0x3f,sizeof(dis));
    dis[st]=0;
    vq[st]=true;
    q.push(st);
    while(!q.empty())
    {
        int now=q.front();
        //printf("now:%d\n",now);
        q.pop();
        vq[now]=false,v[now]++;
        if(v[now]>n)
            printf("NO"),exit(0);
        for(int to,w,i=head[now];i;i=edge[i].nex)
        {
            to=edge[i].to,w=edge[i].w;
            //printf("to:%d %d %d\n",to,dis[to],dis[now]+w);
            if(dis[now]+w<dis[to])
            {
                dis[to]=dis[now]+w;
                //printf("%d %d %d\n",dis[to],dis[now],w);
                if(!vq[to])
                    q.push(to),vq[to]=true;
            }
        }
    }
}
//-------------------//
int main()
{
    scanf("%d%d",&n,&m);
    for(int from,to,w,i=1;i<=m;i++)
    {
        scanf("%d%d%d",&to,&from,&w);
        add(from,to,w);
    }
    for(int i=1;i<=n;i++)
        add(n+1,i,0);
    SPFA(n+1);
    for(int i=1;i<=n;i++)
    {
        if(dis[i]==0x3f3f3f3f)
            dis[i]=0;
        printf("%d ",dis[i]);
    }
    return 0;
}

\(\color{royalblue}{P3275 [SCOI2011] 糖果}\)

将不等式列出之后注意转化、建边细节,这道题用 SPFA 会被卡,需要转化为最长路用拓扑排序求解。

\(\text{Code:}\)

#define LL long long
#define UN unsigned
#include<bits/stdc++.h>
using namespace std;
//--------------------//
const int N=2e5+1,M=5e5+1;

int n,k;
//--------------------//
//Graph
struct Edge
{
    int to,w,nex;
}edge[M][2];
int tot[2],head[N][2];
void add(int from,int to,int w,int op)
{
    edge[++tot[op]][op]={to,w,head[from][op]};
    head[from][op]=tot[op];
    return;
}
struct Poi
{
    int dfn,low;
}p[N];
//----------//
//Tarjan
int dcnt;
int stcnt,sta[N];
bool sv[N];
int scnt,scc[N],sc[N],in[N];
void Tarjan(int now,int fa)
{
    dcnt++,p[now]={dcnt,dcnt},sta[++stcnt]=now,sv[now]=true;
    for(int to,i=head[now][0];i;i=edge[i][0].nex)
    {
        to=edge[i][0].to;
        if(!p[to].dfn)
            Tarjan(to,now),p[now].low=min(p[now].low,p[to].low);
        else if(sv[to])
            p[now].low=min(p[now].low,p[to].dfn);
    }
    if(p[now].dfn==p[now].low)
    {
        scnt++;
        while(sta[stcnt]!=now)
        {
            scc[sta[stcnt]]=scnt;
            sv[sta[stcnt]]=false;
            stcnt--;
            sc[scnt]++;
        }
        scc[sta[stcnt]]=scnt;
        sv[sta[stcnt]]=false;
        stcnt--;
        sc[scnt]++;
    }
    return;
}
//--------------------//
LL f[N];
queue<int>q;
void BFS()
{
    for(int i=1;i<=scnt;i++)
        if(!in[i])
            f[i]=1,q.push(i);
    while(!q.empty())
    {
        int now=q.front();
        q.pop();
        for(int to,w,i=head[now][1];i;i=edge[i][1].nex)
        {
            to=edge[i][1].to,w=edge[i][1].w;
            f[to]=max(f[to],f[now]+w);
            in[to]--;
            if(!in[to])
                q.push(to);
        }
    }
    return;
}
//--------------------//
int main()
{
    scanf("%d%d",&n,&k);
    for(int op,x,y,i=1;i<=k;i++)
    {
        scanf("%d%d%d",&op,&x,&y);
        switch(op)
        {
            case 1:
                add(x,y,0,0),add(y,x,0,0);
                break;
            case 2:
                add(x,y,1,0);
                break;
            case 3:
                add(y,x,0,0);
                break;
            case 4:
                add(y,x,1,0);
                break;
            case 5:
                add(x,y,0,0);
                break;
        }
    }
    for(int i=1;i<=n;i++)
        if(!p[i].dfn)
            Tarjan(i,0);
    for(int i=1;i<=n;i++)
    {
        for(int to,w,j=head[i][0];j;j=edge[j][0].nex)
        {
            to=edge[j][0].to,w=edge[j][0].w;
            if(w&&scc[i]==scc[to])
            {
                printf("-1");
                return 0;
            }
            else if(scc[i]!=scc[to])
                add(scc[i],scc[to],w,1),in[scc[to]]++;
        }
    }
    BFS();
    LL ans=0;
    for(int i=1;i<=scnt;i++)
        ans+=1LL*f[i]*sc[i];//printf("%d %d %d\n",f[i],sc[i],in[i]);
    printf("%lld",ans);
    return 0;
}

2 有向图连通性:强联通分量

2.1 介绍

2.1.1 强连通定义

  • 强连通:对于有向图两点 \(x,y\),若他们互相可达,则称 \(x,y\) 强连通,这种性质为强连通性
  • 强连通图:满足任意两点强连通的有向图称为强连通图
  • 强联通分量:有向图的极大强连通子图称为强联通分量

显著的,强连通性具有传递性,并且强连通的两者等价。当在做某些只关心连通性的问题时,一个强连通分量内所有节点等价,便于做题。

2.1.2 有向图 DFS 树

遍历每一个节点,若此节点未被访问,则以此节点为根进行 DFS,对于整个图搜索完后可以得到一个有向图 DFS 森林。
对于一棵 DFS 树,主要有以下 \(4\) 种边:

  1. 树边:每次从父节点向子节点进行 DFS 的边。
  2. 返祖边:DFS 时访问到当前节点祖先的边。
  3. 横叉边:咕咕咕