电子眼(基环外向图)

前言

为什么我一定要把这么一个特别简单的题目写成博客呢,因为这是我的第一道基环外向图,为什么我一定要写基环外向图呢,因为名字好听呀

题目

阆中市是一个环境优美,气候宜人的小城。因为城市的交通并不繁忙,市内的道路网很稀疏。准确地说,阆中市有 n 条马路和 n 个路口,每条马路连接两个路口,每两个路口之间最多只有一条马路。作为一个交通网络,显然每两个路口之间都是可以到达的。
  为了更好的管理阆中市的交通,市长决定在一些路口假装电子眼,用来随时监察路面情况。这些装在路口的电子眼能够监视所有连接这个路口的马路。现在市长想知道最少需要在多少个路口安装电子眼才能监视到所有的马路。市长已经把所有的路口都编上了1~n的号码。
  给你阆中市的地图,你能帮个忙吗?

【输入格式】

  第一行包括一个数字n,表示阆中市的路口数(马路数)。
  以下n行,第i+1行的第1个数字m表示有m条马路与路口i相连,后面紧接着m个数字,表示与路口i直接连接的路口。

【输出格式】

  输出一个需要安装电子眼的最少的路口数。

【输入样例】

3
2 2 3
2 1 3
2 1 2

【输出样例】

2

【数据范围】

1<=n<=100000

分析

如果是树的话直接用最小覆盖集即可,可这是基环外向图呐。
那么还是很常规的,找环上两点,然后分别直接选择这两点,因为要控制这两点之间的那个点,只是不能确定究竟由哪个点来控制。

代码

/*
基环外向图+最小覆盖集
*/
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+5;
struct edge{
    int to,next;
}E[maxn<<1];
int n,np,first[maxn];
void add(int u,int v)
{
    E[++np]=(edge){v,first[u]};
    first[u]=np;
}
void Init()
{
    scanf("%d",&n);
    int m,v;
    for(int u=1;u<=n;u++)
    {
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d",&v);
            add(u,v);
        }
    }
}
int u1,u2,ans;
bool vis[maxn],s[maxn],mark[maxn];
void DFS0(int i,int f)//找环一定不能找错 
{
    vis[i]=1;
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f)continue;
        if(vis[j])
        {
            u1=i;
            u2=j;
            continue;
        }
        DFS0(j,i);
    }
}
int cnt;
void DFS(int i,int f)
{
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f)continue;
        if( (i==u1&&j==u2) || (i==u2&&j==u1) )continue;

        DFS(j,i);
        if(!mark[j])
        {
            if(!s[i])
            {
                s[i]=1;
                cnt++;
                mark[i]=mark[j]=1;
            }
        }
    }
}
int get_ans(int x)
{
    cnt=1;
    memset(mark,0,sizeof(mark));
    memset(s,0,sizeof(s));
    s[x]=1;
    DFS(x,0);
    return cnt;
}
int main()
{
    //freopen("in.txt","r",stdin);
    Init();
    DFS0(1,0);
    ans=min(get_ans(u1),get_ans(u2));
    printf("%d\n",ans);
    return 0;
}

注意:
1、寻找环实际上可以当做在图上寻找环,因为确定有且只有一个环,所以要简单一些,利用返祖边找环,还要拒绝父亲节点。
2、去掉的那条边必须要标记,我认为直接单独判断走的边是否链接断掉的两点即可

附录:基环外向树和基环内向树

区别:
有向图:
基环内向树是出度全为1,基环外向树是入度全为1。

也就是可以把图画作一个收尾相接的圈,然后圈上每个点都可以作为跟延伸一颗树出来
基环内向图是树上节点往环上指,基环外向图是环上结点往外指。

处理:
一般来说先找环,然后随便找个环上的边删掉破环当做树来处理,可以利用一些题目性质不用枚举每条被删除的边
找环操作实际上是无向图找最小环,这里可以是随便一个环,基本思想是跑一棵树遇到返祖边则成环(区别父边)

你可能感兴趣的:(树)