Tarjan的证明(bushi)

以下是lyn学长在今天为我们这些经冰镪酱讲解Tarjan模板时所用的PPT

第一页

前方高能请注意

第二页

将复杂图上问题转化为相对简单的特殊图上问题。

        有向图:找强连通分量(scc)

                缩scc?

                        DAG

无向图:找割点、桥(点双、边双)

        缩点双、边双?

                圆方树,树

———————————————————————————————————————————然后就是一大堆概念……不说了

但是呢,有一个问题,很是引起了我的兴趣:

给一个有向图,如果你手动标记了一个点,那么这个点以及它所能到达的所有点都被标记。

问:

1. 最少手动标记多少个点使得所有点被标记

2. 最少加入多少条有向边使得只需要手动标记任意一个点就能标记所有点

n≤100000

很好想对不对,反正我们九个人讨论了将近一个小时

答案的确不难

1.入度为0的点的个数

2.max(入度为0的点的个数,出度为0的点的个数),既然n≤100000,那么数据范围其实也是无所谓的对吧?(bushi

思路分析

首先一般来看,是当前的图来说的话不好做,我们既然已经学了Tarjan算法,我们就可以先把他缩图成一个有向无环图来做(这是final版本)

假设转换为之后的有向无环图总共有p个起点和q个终点,可以给图中的每一个起点进行标记,那么图中的其余点都是可以被标记的,而题目中要求解至少添加多少条边,相当于是至少给多少个起点进行标记会使其余所有点可以被标记,所以至少需要给p个起点标记,也即第一问的答案是p。

对于第二问其实问的是我们需要在原图中添加多少条边使其变成一个强连通分量(图中任意两点可以相互到达),我们将原图转换为有向无环图之后需要添加至少max(p,q)条边使其变为一个强连通分量,这个结论比较重要,但是证明的过程很难,我们记住这个结论就可以了,我写在后面就可以了。记住吧,题目的核心是使用tarjan算法将原图转换为有向无环图,包含两个步骤:① 求解强连通分量,在dfs的过程中求解出每一个强连通分量并且将强连通分量中的所有点标记在对应的强连通分量的编号中;② 缩点,遍历所有节点,然后遍历当前点的所有邻接点,然后统计每一个强连通分量整体的入度和出度,最后遍历一下每一个强连通分量计算入度和出度为0的个数,输出答案即可。

证明:

证明如下:

不妨设p ≥ q (如果p < q那么可以将图的每条边反个向即可)。如果q = 1 ,那么将出度为0 的点连p 条边到p 个入度为0 的点,显然是一种方案,并且,如果只连少于p 条边,那么入度为0 的p 个点里必然有其中某个点没有入边,那么这个点将是到不了的,矛盾;如果q > 1 ,则必然能找到q 个入度为0 的点,使得这些点逐个可以到达q 个出度为0 的点,且一一对应。如若不然,则说明p 个入度为0 的点都走不到某个出度为0 的点,这与缩点后的图是有向无环图矛盾。挑一个出度为0 的点u 和入度为0的点v ,连一条边u → v ,这就会把规模( p , q ) 的问题转为规模( p − 1 , q − 1 ) 的问题,后者的解是max ⁡ { p , q } − 1 ,所以原问题的解的上界就是max ⁡ { p , q } = p 。如果解小于p ,同样会使得至少一个入度0 的点到不了,矛盾,即原问题得证(完结撒花)。

代码如下

//求割点
void dfs(int u,int fa)
{
	low[u] = dfn[u] = dfs_clock ++;
	int c = 0;
	for(int i = head[u];i;i = nxt[i])
	{
		int v = to[i];
		if(!dfn[v])
		{
			dfs(v,u);
			c ++;
			low[u] = min(low[u],low[v]);
			if(low[v] >= dfn[u])
			cut[u] = 1;
		}
		else
		if(dfn[v] < dfn[u] && v != fa)
		low[u] = min(low[u],dfn[v]);
	}
	if(c == 1 && fa == 0)
	cut[u] = 0;
}
int main() 
{
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1;i <= n;i ++) 
	{
        int t;
        while(cin >> t, t) 
		add(i, t);
    }
    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 a = id[i], b = id[k];
            if(a != b) 
			{
                dout[a]++;
                din[b]++;
            }
        }
    int a = 0, b = 0;
    for(int i = 1;i <= scc_cnt;i ++) 
	{
        if(!din[i]) 
		a++;
        if(!dout[i]) 
		b++;
    }
    cout << a;
    if (scc_cnt == 1)
    cout << 0;
    else
    cout << max(a,b);
    return 0;
}

自测样例似乎是过了,对不对再说吧,在调吧

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值