• 【bzoj1093】[ZJOI2007]最大半连通子图 Tarjan+拓扑排序+dp


    题目描述

    一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:对于u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。若G'=(V',E')满足V'是V的自己,E'是E中所有跟V'有关的边,则称G'是G的一个导出子图。若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图。若G'是G所有半连通子图中包含节点数最多的,则称G'是G的最大半连通子图。给定一个有向图G,请求出G的最大半连通子图拥有的节点数K,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出C对X的余数。

    输入

    第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤100000, M ≤1000000;对于100%的数据, X ≤10^8

    输出

    应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.

    样例输入

    6 6 20070603
    1 2
    2 1
    1 3
    2 4
    5 6
    6 4

    样例输出

    3
    3


    题解

    Tarjan+拓扑排序+dp

    显然,如果原图是一个DAG,那么选择的就是一条链,答案就是最长链;

    如果不是呢?Tarjan缩点,然后拓扑排序+dp求带权最长链即可。正确性显然。

    需要注意的是缩完点后如果有重边需要只考虑一条的贡献,因为确定了点就确定了边的选择,只有一次转移的机会。

    时间复杂度 $O(n+m)$ 

    #include <queue>
    #include <cstdio>
    #include <cctype>
    #include <vector>
    #include <cstring>
    #define N 100010
    using namespace std;
    queue<int> q;
    vector<int> e[N] , v[N];
    int p , deep[N] , low[N] , tot , ins[N] , sta[N] , top , bl[N] , si[N] , num , ind[N] , last[N];
    struct data
    {
    	int x , y;
    	data(int a = 0 , int b = 0) {x = a , y = b;}
    	data operator+(int a) {return data(x + a , y);}
    	data operator^(data a)
    	{
    		if(x > a.x) return *this;
    		else if(x < a.x) return a;
    		else return data(x , (y + a.y) % p);
    	}
    }f[N];
    void tarjan(int x)
    {
    	vector<int>::iterator i;
    	deep[x] = low[x] = ++tot , ins[x] = 1 , sta[++top] = x;
    	for(i = e[x].begin() ; i != e[x].end() ; i ++ )
    	{
    		if(!deep[*i]) tarjan(*i) , low[x] = min(low[x] , low[*i]);
    		else if(ins[*i]) low[x] = min(low[x] , deep[*i]);
    	}
    	if(deep[x] == low[x])
    	{
    		int t;
    		num ++ ;
    		do
    		{
    			t = sta[top -- ] , ins[t] = 0;
    			bl[t] = num , si[num] ++ ;
    		}while(t != x);
    	}
    }
    void solve(int n)
    {
    	vector<int>::iterator i;
    	data ans;
    	int x;
    	for(x = 1 ; x <= n ; x ++ )
    		for(i = e[x].begin() ; i != e[x].end() ; i ++ )
    			if(bl[x] != bl[*i])
    				v[bl[x]].push_back(bl[*i]) , ind[bl[*i]] ++ ;
    	for(x = 1 ; x <= num ; x ++ )
    		if(!ind[x])
    			f[x] = data(si[x] , 1) , q.push(x);
    	while(!q.empty())
    	{
    		x = q.front() , q.pop() , ans = ans ^ f[x];
    		for(i = v[x].begin() ; i != v[x].end() ; i ++ )
    		{
    			if(last[*i] != x) last[*i] = x , f[*i] = f[*i] ^ (f[x] + si[*i]);
    			ind[*i] -- ;
    			if(!ind[*i]) q.push(*i);
    		}
    	}
    	printf("%d
    %d
    " , ans.x , ans.y);
    }
    inline char nc()
    {
    	static char buf[100000] , *p1 , *p2;
    	return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ;
    }
    inline int read()
    {
    	int ret = 0; char ch = nc();
    	while(!isdigit(ch)) ch = nc();
    	while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ '0') , ch = nc();
    	return ret;
    }
    int main()
    {
    	int n = read() , m = read() , i , x , y;
    	p = read();
    	for(i = 1 ; i <= m ; i ++ ) x = read() , y = read() , e[x].push_back(y);
    	for(i = 1 ; i <= n ; i ++ )
    		if(!deep[i])
    			tarjan(i);
    	solve(n);
    	return 0;
    }
    

     

  • 相关阅读:
    [转]在Windows 7 X64系统中安装SharePoint 2010
    使用CUBE和ROLLUP对数据进行汇总
    SQL Server、Oracle数据库排序空值null问题解决办法
    解释传统与敏捷方法最贴切的故事:大象与猴子
    3个简单的问题教会你如何做人、做事、做学问
    DOS命令行方式使用FTP实战练习
    SQL四种语言:DDL,DML,DCL,TCL
    如何对软件需求进行测试
    Windows中的句柄(handle)
    软件静默安装参数
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7888921.html
Copyright © 2020-2023  润新知