专题1:图论

专题1:图论

一、tarjan

我在之前的博客中详细介绍过,在这里直接放一个神奇的链接
首先了解强连通分量是什么,之后的就会很好理解

强连通分量

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量。(来源:百度百科)
是不是根本没看懂,画几个图就好了,我们就以最经典的图(我看见了一堆关于tarjan的文章,都拿这个做样例,我也凑个热闹,毕竟我第一次接触时也用的这个)为例。



图片中的1,2,3,4在一起叫一个强联通分量

了解tarjan的运行过程

这个真的是费了巨长的时间,后来才发现整个过程都在进行一项工作,就是维护DFN数组和LOW数组。DFN[u]代表u的搜索次序编号(就是发现u的时间戳),LOW[u]表示的是u或u的子树能够追溯到的最早的栈中节点的次序号,那么我们从每一个细节开始演示tarjan的过程。

1.先去发现各个节点

和普通的搜索一样,就是从每一个未被发现的点开始进行搜索遍历,发现一个未被发现的点就直接把它进栈,然后接着更新该点的DFN值(你可能会问,那如果我发现了被发现过的点怎么办呢??别急,看下面)

2.更新LOW值

在发现被发现过的点之后就要更新自己的LOW值,因为LOW对应的是最小的值,并且栈中的一定是被发现过的(如果这个点被发现现过,而且被出栈过并且现在不在栈中,就证明这个点也无法再次被更新了),所以说更新的便是被发现的点的DFN值和自己的LOW值中小的那一个,也就是说这个更新的条件就是这个点在栈中。

3.回溯中的更新与出栈

回溯时要完成两个操作,一个是更新栈中所有点的LOW值,就是非常简单的回溯时把自己的LOW更新成下一点和自己中较小的那一个LOW值。第二个便是出栈,出栈的时候没什么好说的,遇见自己的DFN值和LOW值相等的就直接出栈,因为它们和别人也没什么关系,没和其他任何点构成强连通分量。

代码实现

void tarjan(int x)
{
    exist[x]=1;
    DFN[x]=LOW[x]=++tot;
    stack[++index]=x;
    for(int i=0;i<G[x].size();i++)
    {
        int v=edge[G[x][i]].to;
        if(DFN[v]==0)
        {
            tarjan(v);
            LOW[x]=min(LOW[x],LOW[v]);
        }
        else if(exist[v]==1)//这个就是是否在栈中的意思
        {
            LOW[x]=min(LOW[x],DFN[v]);
        }
    }
    if(DFN[x]==LOW[x])
    {
        int k=0,t;
        color++;//强连通分量序号
        do//出栈
        {
            t=stack[index];
            stack[index]=0;
            index--;
            a[t]=color;//每个结点的强连通分量序号
            exist[t]=0;
            k++;
        }while(x!=t);
        sum[color]=k;//每个强连通分量中节点的个数
    }
}

二、2-SAT

2-SAT 问题是这样的:有 n 个布尔变量xi,另有 m 个需要满足的条件,每个条件的形式都是“xi为真/假或者 xj 为真/假”。比如:“ x1 为真或者 x3 为假”、“ x7 为假或者 x2 为假”都是合法的条件。注意这里的“或”是指两个条件至少有一个是正确的,比如 x1 x3 一共有3种组合满足“ x1 为真或者 x3 为假”,即 x1 x3 真、 x1 x3 假、 x1 x3 假。2-SAT 问题的目标是给每个变量赋值,使得所有条件得到满足。
对于这种问题,最常见的做法是拆点,把一个节点 i 拆分成2i和2 i +1,分别表示xi为真和 xi 为假。最后要为每个结点打一个标记(只能打一个标记)。
对于“ xi 为假或者 xj 为假”这样的条件,我们连一条有向边2 i +12 j ,表示如果标记结点2i+1那么也必须标记结点 j (因为如果xi为真,则 xj 必须为假才能使条件成立)。同理,还需要连一条有向边2 j +12 i <script type="math/tex" id="MathJax-Element-108">i</script>。话句话说,每个条件对应两条“对称”的边。
接下来注意考虑每个没有赋值的变量,先设定为假,然后逐步递归,寻找矛盾关系,如果矛盾,则假设为真,如果还有矛盾,那就证明该2-SAT 问题无解。
因为网上有很多tarjan版本的,所以我在这里给大家提供一种dfs版本的板子,同时也是HDU3062的题解

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
bool mark[4005];
vector<int> G[4005];
int s[4005],c;
int n,m;
void Addedge(int x,int xval,int y,int yval)
{
    x=(x-1)*2+xval;
    y=(y-1)*2+yval;
    G[x^1].push_back(y);
    G[y^1].push_back(x);
}
bool dfs(int x)
{
    if(mark[x^1]) return false;
    if(mark[x]) return true;
    mark[x]=true;
    s[c++]=x;
    for(int i=0;i<G[x].size();i++)
        if(!dfs(G[x][i])) return false;
    return true;
}
bool solve()
{
    for(int i=0;i<n*2;i+=2)
    {
        if(!mark[i] && !mark[i+1])
        {
            c=0;
            if(!dfs(i))
            {
                while(c>0) mark[s[--c]]=false;
                if(!dfs(i+1)) return false;
            }
        }
    }
    return true;
}
int main()
{
    freopen("hdu3062.in","r",stdin);
    freopen("hdu3062.out","w",stdout);
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=m;i++)
        {
            int a1,a2,c1,c2;
            scanf("%d%d%d%d",&a1,&a2,&c1,&c2);
            Addedge(a1,c1^1,a2,c2^1);
        }
        if(solve()) printf("YES\n");
        else printf("NO\n");
        for(int i=0;i<n*2;i++)
            G[i].clear();
        memset(mark,false,sizeof(mark));
    }
    return 0;
}

以上知识为集训时训练中的图论算法,其他的算法(如网络流等)不在这个专题中,会在以后详细介绍
下面推荐几道练习题,并附上题解

poj1236 Network of Schools

Tarjan缩点后对缩点后新图扫每个强连通块的入度出度
对于子任务A:求需要给多少个学校发软件。即为求有多少个入度为0的点。因为对于对于每个入度不为0的点一定可以从一个其他点走到.
对于子任务B:求入度为0的点个数与出度为0的点个数的最大值,需要添多少条边才能形成使所有点都可以被某个点到达,所以对于每个出度为0的点,需要拓展一条出边,对于每个入度为0的点,需要拓展一条入边,为了减少拓展边数,可以将所有出度为0的点拓展出边到某个入度为0的点的上,最后答案即为 max(入度为0点数,出度为0点数)。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
using namespace std;
struct apple
{
    int u,v;
    apple(int x,int y){u=x;v=y;}
};
int n,tot,index,color,ans1,ans2;;
const int inf=0x3f3f3f3f;
vector<apple> edge;
vector<apple> edge1;
vector<int> G[105];
vector<int> P[105];
int a[105],stack[105],DFN[105],LOW[105],in[105],out[105];
void Addedge(int u,int v)
{
    edge.push_back(apple(u,v));
    int m=edge.size();
    G[u].push_back(m-1);
}
void Addedge1(int u,int v)
{
    edge1.push_back(apple(u,v));
    int m=edge1.size();
    P[u].push_back(m-1);
}
int exist(int x)
{
    for(int i=1;i<=index;i++)
    {
        if(stack[i]==x) return 1;
    }
    return 0;
}
void tarjan(int x)
{
    DFN[x]=LOW[x]=++tot;
    stack[++index]=x;
    for(int i=0;i<G[x].size();i++)
    {
        int v=edge[G[x][i]].v;
        if(DFN[v]==0)
        {
            tarjan(v);
            LOW[x]=min(LOW[x],LOW[v]);
        }
        else if(exist(v)==1)
        {
            LOW[x]=min(LOW[x],DFN[v]);
        }
    }
    if(DFN[x]==LOW[x])
    {
        int t;
        color++;
        do
        {
            t=stack[index];
            stack[index]=0;
            index--;
            a[t]=color;
        }while(t!=x);
    }
}
int main()
{
    freopen("network.in","r",stdin);
    freopen("network.out","w",stdout);
    scanf("%d",&n);
    for(int u=1;u<=n;u++)
    {
        int v;
        while(scanf("%d",&v) && v!=0)
            Addedge(u,v);
    }
    for(int i=1;i<=n;i++)
        if(DFN[i]==0) tarjan(i);
    for(int i=0;i<edge.size();i++)
        if(a[edge[i].u]!=a[edge[i].v])
            Addedge1(a[edge[i].u],a[edge[i].v]);
    for(int i=0;i<edge1.size();i++)
    {
        int u=edge1[i].u,v=edge1[i].v;
        out[u]++;in[v]++;
    }
    for(int i=1;i<=color;i++)
    {
        if(!in[i]) ans1++;
        if(!out[i]) ans2++;
    }
    printf("%d\n",ans1);
    if(color!=1) printf("%d\n",max(ans1,ans2));
    else printf("0\n");
    return 0;
}

bzoj1823 满汉全席

2-sat 模板题,感觉和HDU3062无任何区别,翻译一下题意就可以发现了,每个材料可以做成满菜和汉菜,就相当于一真一假。处理方式同HDU3062

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
bool mark[40005];
vector<int> G[40005];
int s[40005],c;
int t,n,m;
char getc()
{
    char ch=getchar();
    while(ch>'z'||ch<'a')ch=getchar();
    return ch;
}
void Addedge(int x,int xval,int y,int yval)
{
    x=(x-1)*2+xval;
    y=(y-1)*2+yval;
    G[x^1].push_back(y);
    G[y^1].push_back(x);
}
bool dfs(int x)
{
    if(mark[x^1]) return false;
    if(mark[x]) return true;
    mark[x]=true;
    s[c++]=x;
    for(int i=0;i<G[x].size();i++)
        if(!dfs(G[x][i])) return false;
    return true;
}
bool solve()
{
    for(int i=0;i<n*2;i+=2)
    {
        if(!mark[i] && !mark[i+1])
        {
            c=0;
            if(!dfs(i))
            {
                while(c>0) mark[s[--c]]=false;
                if(!dfs(i+1)) return false;
            }
        }
    }return true;
}
int main()
{
    freopen("bzoj1823.in","r",stdin);
    freopen("bzoj1823.out","w",stdout);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            char c1,c2;
            int tmp1,tmp2,a1,a2;
            c1=getc();
            scanf("%d",&a1);
            c2=getc();
            scanf("%d",&a2);
            if(c1=='m') tmp1=1;else tmp1=0;
            if(c2=='m') tmp2=1;else tmp2=0;
            Addedge(a1,tmp1,a2,tmp2);
        }
        if(solve()) printf("GOOD\n");
        else printf("BAD\n");
        for(int i=0;i<n*2;i++)
            G[i].clear();
        memset(mark,false,sizeof(mark));
    }
    return 0;
}

poj3170

暴搜,从起点搜索每个woods,从骑士搜索每个woods,然后枚举每个woods,输出答案

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
using namespace std;
struct dot
{
    int x,y,val;
};
int w,h,ans=0x7fffffff;
int map[1005][1005],vist[1005][1005];
int sx,sy,ex,ey;
int b[1005][1005],k[1005][1005];
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
void bfsb()
{
    queue<dot> q;
    dot tmp;
    tmp.x=sx;tmp.y=sy;tmp.val=0;
    q.push(tmp);
    while(!q.empty())
    {
        dot u=q.front();q.pop();
        int x=u.x,y=u.y,val=u.val;
        for(int i=0;i<4;i++)
        {
            int newx=x+dx[i],newy=y+dy[i];
            if(newx>=1 && newx<=h && newy>=1 && newy<=w && map[newx][newy]!=1 && !vist[newx][newy])
            {
                tmp.x=newx;tmp.y=newy;tmp.val=val+1;
                vist[newx][newy]=1;
                if(map[newx][newy]==4) b[newx][newy]=val+1;
                q.push(tmp);
            }
        }
    }
}
void bfsk()
{
    queue<dot> q;
    dot tmp;
    tmp.x=ex;tmp.y=ey;tmp.val=0;
    q.push(tmp);
    while(!q.empty())
    {
        dot u=q.front();q.pop();
        int x=u.x,y=u.y,val=u.val;
        for(int i=0;i<4;i++)
        {
            int newx=x+dx[i],newy=y+dy[i];
            if(newx>=1 && newx<=h && newy>=1 && newy<=w && map[newx][newy]!=1 && !vist[newx][newy])
            {
                tmp.x=newx;tmp.y=newy;tmp.val=val+1;
                vist[newx][newy]=1;
                if(map[newx][newy]==4) k[newx][newy]=min(k[newx][newy],val+1);
                q.push(tmp);
            }
        }
    }
}
int main()
{
    freopen("bzoj1671.in","r",stdin);
    freopen("bzoj1761.out","w",stdout);
    scanf("%d%d",&w,&h);
    for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++)
        {
            scanf("%d",&map[i][j]);
            if(map[i][j]==2) sx=i,sy=j;
            if(map[i][j]==3) ex=i,ey=j;
        }
    memset(b,63,sizeof(b));
    memset(k,63,sizeof(k));
    bfsb();
    memset(vist,0,sizeof(vist));
    bfsk();
    for(int i=1;i<=h;i++)
        for(int j=1;j<=w;j++)
            if(map[i][j]==4) ans=min(ans,b[i][j]+k[i][j]);
    printf("%d",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值