VIJOS-P1325 桐桐的糖果计划

VIJOS-P1325 桐桐的糖果计划

JDOJ 1432 桐桐的糖果计划

https://neooj.com/oldoj/problem.php?id=1432

Description

桐桐很喜欢吃棒棒糖。他家处在一大堆糖果店的附近。 但是,他们家的区域经常出现塞车、塞人等情况,这导致他不得不等到塞的车或人走光了他才能去买到他最爱吃的棒棒糖品种。于是,他去找市长帮他修路,使得每两个糖果店之间至少有两条完全不同的路。可是市长经费有限,于是让桐桐找出哪些路被塞住后会使某些糖果店与糖果店间无法到达及最少的修路条数。你能帮助他吃到他最喜爱的糖果吗? 注:1-> 3-> 2    和  1-> 3-> 4-> 2  为不完全不同的路,即不符合题意的路。         1-> 3-> 4-> 2  和  1-> 5-> 2  为完全不同的路,即符合题意的路。

Input

输入第一行是两个数n,m(n< =5000,m< =10000) 接下来的m行,每行两个数i,j,表示i,j间有一条边连接。

Output

输出有两行。第一行为塞住后就不可以到达某些糖果店的道路条数,第二行为最少的修路条数。

Sample Input

7 7 1 2 2 3 3 4 2 5 4 5 5 6 5 7

Sample Output

3 2 
 
这道题比较容易分析,在草纸上画完图之后可以很明确地分析出来第一行就是在问整个无向图中桥的个数
桥也叫做割边,就是无向图中能够使整张图分裂成两个不连通子图的边。
第二问就是求缩点之后,需要继续添加多少条边,才能使新图变成边双联通图。
 
我们选择使用tarjan算法,那么问题就是,如何在使用tarjan求有向图强连通分量的基础上来求割边?
附带我写的tarjan模板,供下一步理解和参考。
void tarjan(int x)//tarjan算法模板(链式前向星)
{
    z[++top]=x;
    v[x]=1;
    inz[x]=1;
    for(int i=head[x];i;i=nxt[i])
    {
        int y=to[i];
        if(v[y]==0)
            tarjan(to[i]),low[x]=min(low[x],low[to[i]);
        else if(inz[to[i]]==1)
            low[x]=min(low[x],deep[to[i]]);
    }
    if(deep[x]==low[x])
    {
        ans++;
        int t;
        do
        {
            t=z[top--];
            inz[t]=0;
            f[ans][++f[ans][0]]=t;
        }while(t!=x)
    }
}

说明一下,deep数组表示时间戳,即节点x被搜索的次序编号。

low数组表示x能通过其他边回到的x的祖先,标记的祖先最小的时间戳。

深搜回溯到当前节点之后,当deep[x]==low[x],以x为根的搜索子树上的所有节点为一个强连通分量。

z数组模拟数据结构栈,inz数组表示元素是否在栈中,v数组表示元素是否被搜索过。

f数组记录每一个强连通分量里元素的个数和所有元素。

 

好了,下面我们介绍用tarjan算法求割边。

假如low[y]>deep[x]的时候,说明y不仅不能到达x的祖先,也不能通过另外一条边直接直接直接到达x,说明它们之间的e(x,y)便是割边。

注意处理重边。

处理重边的方式有两种,依据重边有用没用而定(而有用没用依题目而定)(等于没说)

假如重边没有用,在tarjan的时候加一个参数记录它的父亲,注意是参数,当y遇到父亲节点时不拓展回去,保证重边不会被遍历。

假如重边有用,那么增加一个参数记录边的编号,y不能通过这条边访问其父亲节点,但是却可以通过重边访问x,保证重边会被遍历到。

 

那么好了,我们如何来判定重边?

 

这里要引入对偶边的判定。

位运算异或,请小伙伴们自行补习。

void tarjan(int x,int pre)
{
    v[x]=true;
    deep[x]=low[x]=++tot;
    for(int i=head[x];i!=-1;i=next[i])
    {
        int y=to[i];
        if( (i^1)==pre ) continue;
        if(!deep[y])
        {
            tarjan(y,i);
            low[x]=min(low[x],low[y]);
            if(low[y]>deep[x])  //割边; 
        }
        else low[x]=min(low[x],deep[y]);
    } 
}

 

好了我们来针对一下这道题,第一问通过刚才的讲解已经没什么难度了。

来看第二问,这里注意我们是求缩点后需要加多少条边。

这里我们需要画图理解。

缩点后的无向图会变成一棵树,统计度为2的那些双联通分量就是叶子结点(双向边)。

然后ans=(t+1)/2;

好了讲了这么多相信大家也会对这个有一定的理解,细节的实现可以自己慢慢调。下面的代码仅供参考

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 10010
using namespace std;
int n,m,num,head[maxn],low[maxn],deep[maxn],topt;
int top,z[maxn],inz[maxn],ans,sum,belong[maxn],rudu[maxn];
struct node
{   
    int v,pre;
}e[maxn*2];
void add(int from,int to)
{
    e[num].v=to;
    e[num].pre=head[from];
    head[from]=num++;
}
void tarjan(int x,int fa)
{
    low[x]=deep[x]=++topt;
    z[++top]=x;inz[x]=1;
    for(int i=head[x];i!=-1;i=e[i].pre)
    {
        int v=e[i].v;
        if(i==(fa^1))
            continue;
        if(deep[v]==0)
        {
            tarjan(v,i);
            low[x]=min(low[x],low[v]);
            if(low[v]>deep[x])ans++;
        }
        else if(inz[v])
            low[x]=min(low[x],deep[v]);
    }
    if(low[x]==deep[x])
    {
        sum++;
        while(x!=z[top])
        {
            belong[z[top]]=sum;
            inz[z[top]]=0;
            top--;
        }
        belong[z[top]]=sum;
        inz[z[top]]=0;
        top--;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    int u,v;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;i++)
        if(deep[i]==0)
            tarjan(i,-1);
    printf("%d\n",ans);
    ans=0;
    for(int u=1;u<=n;u++)
        for(int i=head[u];i!=-1;i=e[i].pre)
        {
            int v=e[i].v;
            if(belong[u]!=belong[v])
                rudu[belong[u]]++;
        }
    for(int i=1;i<=sum;i++)
        if(rudu[i]==1)
            ans++;
    printf("%d\n",(ans+1)/2);
}

 

posted @ 2019-07-10 20:38  Seaway-Fu  阅读(195)  评论(1编辑  收藏  举报