1175 最大半连通子图(强连通分量)

2023-05-16

1. 问题描述:

一个有向图 G=(V,E) 称为半连通的 (Semi-Connected),如果满足:∀u,v∈V,满足 u→v 或 v→u,即对于图中任意两点 u,v,存在一条 u 到 v 的有向路径或者从 v 到 u 的有向路径。若 G′=(V′,E′) 满足,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 到 N,保证输入中同一个 (a,b) 不会出现两次。

输出格式

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

数据范围

1 ≤ N ≤ 10 ^ 5,
1 ≤ M ≤ 10 ^ 6,
1 ≤ X ≤ 10 ^ 8

输入样例:

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

输出样例:

3
3
来源:https://www.acwing.com/problem/content/description/1177/

2. 思路分析:

分析题目可以知道半连通分量指的是节点u可以走到v或者节点v可以走到u,也即至少有一个是成立的,我们需要求解最大半连通子图的节点个数以及对应的方案数目,如果我们在一个强连通分量中,可以发现选择强连通分量中的某些点不如选择整个强连通分量中的所有点,因为强连通分量中的所有点都是连通的,所以选择全部点更优,我们可以先把所有强连通分量找出来,求解强连通分量可以使用tarjan算法,也即在dfs遍历的过程中找出所有的强连通分量,将每一个强联通分量中点的编号标记在对应的强连通分量中编号中(使用idx来记录每一个点所在的强连通分量),并且记录每一个强连通分量中点的个数,求解完强连通分量之后接下来就是缩点,因为这道题目需要求解最大半连通子图中点的个数以及对应的方案数目,而我们使用tarjan算法求解强连通分量,缩点以及建图之后那么得到的是一个有向无环图,也即新建的图是满足拓扑序的,可以发现本质上求解的是拓扑图中的最长链,而且图是无环的所以我们可以使用递推的方式来求解,并且因为求解我们的是最长链的方案数目,所以类似于之前求解最优方案的思路,我们可以使用一个数组g来记录方案数目,在求解最长链的时候维护这个方案数目。

这里需要注意一些细节上的问题,因为在递推是按照边的方式进行递推的,也即我们枚举的是边,通过边来转移的,所以在缩点之后的建图需要注意不要重复建边(也即一个强连通分量中的一条边只能够向另外个强连通分量连一条边),因为如果有重边的话会多计算一些方案数目,题目中只有点不同的时候才属于不同的方案,如下图所示:

 当我们建好图之后那么递推求解最长链即可,因为需要求解最长链对应的方案数目,所以需要维护两个数组f和g,f[i]维护到强连通分量编号为i的最大节点个数,g[i]维护到强联通分量编号为i的最大节点个数对应的方案数目,枚举的时候按照强连通分量编号逆序枚举,为什么呢??这个其实与tarjan算法求解每一个强连通分量有关,如下图所示,可以发现在使用tarjan算法求解之后每一个强连通分量都是按照逆序的顺序排序的,最终最长链肯定是在f数组的第一个位置,所以需要使用逆序的顺序进行递推,这里求解最长链的思路与背包问题求解方案数目的思路是一样的,分为两种情况更新,看当前的点可以更新哪些邻接点:

  • f[next] > f[i] + size[k],f[next] = f[i] + size[k],g[next] = g[i] 
  • f[next] = f[i] + size[k],g[next] += g[i]

 

3. 代码如下:

c++(y总):

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_set>

using namespace std;

typedef long long LL;

const int N = 100010, M = 2000010;

int n, m, mod;
int h[N], hs[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, scc_size[N];
int f[N], g[N];

void add(int h[], int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u, in_stk[u] = true;

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top -- ];
            in_stk[y] = false;
            id[y] = scc_cnt;
            scc_size[scc_cnt] ++ ;
        } while (y != u);
    }
}

int main()
{
    memset(h, -1, sizeof h);
    memset(hs, -1, sizeof hs);

    scanf("%d%d%d", &n, &m, &mod);
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(h, a, b);
    }

    for (int i = 1; i <= n; i ++ )
        if (!dfn[i])
            tarjan(i);

    unordered_set<LL> S;    // (u, v) -> u * 1000000 + v
    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];
            LL hash = a * 1000000ll + b;
            if (a != b && !S.count(hash))
            {
                add(hs, a, b);
                S.insert(hash);
            }
        }

    for (int i = scc_cnt; i; i -- )
    {
        if (!f[i])
        {
            f[i] = scc_size[i];
            g[i] = 1;
        }
        for (int j = hs[i]; ~j; j = ne[j])
        {
            int k = e[j];
            if (f[k] < f[i] + scc_size[k])
            {
                f[k] = f[i] + scc_size[k];
                g[k] = g[i];
            }
            else if (f[k] == f[i] + scc_size[k])
                g[k] = (g[k] + g[i]) % mod;
        }
    }

    int maxf = 0, sum = 0;
    for (int i = 1; i <= scc_cnt; i ++ )
        if (f[i] > maxf)
        {
            maxf = f[i];
            sum = g[i];
        }
        else if (f[i] == maxf) sum = (sum + g[i]) % mod;

    printf("%d\n", maxf);
    printf("%d\n", sum);

    return 0;
}

python(由于数据规模太大了,所以递归的深度很大所以超时了,只过了8个数据):

from typing import List
import sys


class Solution:
    stk, in_stk, size, idx, timestamp, top, ssc_cnt = None, None, None, None, None, None, None

    # tarjan算法求解强连通分量
    def tarjan(self, u: int, dfn: List[int], low: List[int], g: List[List[int]]):
        dfn[u] = low[u] = self.timestamp + 1
        self.timestamp += 1
        self.stk[self.top + 1] = u
        self.top += 1
        self.in_stk[u] = 1
        for next in g[u]:
            if dfn[next] == 0:
                self.tarjan(next, dfn, low, g)
                low[u] = min(low[u], low[next])
            elif self.in_stk[next] == 1:
                low[u] = min(low[u], dfn[next])
        if dfn[u] == low[u]:
            self.ssc_cnt += 1
            while True:
                t = self.stk[self.top]
                self.top -= 1
                self.in_stk[t] = 0
                self.idx[t] = self.ssc_cnt
                self.size[self.ssc_cnt] += 1
                if t == u: break

    def process(self):
        n, m, mod = map(int, input().split())
        # g1表示原图
        g1 = [list() for i in range(n + 10)]
        for i in range(m):
            a, b = map(int, input().split())
            g1[a].append(b)
        dfn, low = [0] * (n + 10), [0] * (n + 10)
        self.stk, self.in_stk, self.idx, self.size = [0] * (n + 10), [0] * (n + 10), [0] * (n + 10), [0] * (n + 10)
        self.timestamp = self.ssc_cnt = self.top = 0
        for i in range(1, n + 1):
            if dfn[i] == 0:
                self.tarjan(i, dfn, low, g1)
        # 缩点, 建图
        dic = dict()
        # g2为新建的图
        g2 = [list() for i in range(self.ssc_cnt + 1)]
        for i in range(1, n + 1):
            for next in g1[i]:
                a, b = self.idx[i], self.idx[next]
                hash = a * 10000000 + b
                # 如果当前连的边之后是没有连过那么则建一条边
                if self.idx[i] != self.idx[next] and hash not in dic:
                    g2[a].append(b)
                    dic[hash] = 1
        f, g = [0] * (self.ssc_cnt + 1), [0] * (self.ssc_cnt + 1)
        # 按照拓扑序逆推
        for i in range(self.ssc_cnt, 0, -1):
            if f[i] == 0:
                f[i] = self.size[i]
                g[i] = 1
            for next in g2[i]:
                if f[next] < f[i] + self.size[next]:
                    f[next] = f[i] + self.size[next]
                    g[next] = g[i]
                elif f[next] == f[i] + self.size[next]:
                    f[next] = f[i] + self.size[next]
                    g[next] = (g[next] + g[i]) % mod
        maxf = s = 0
        # 枚举答案以及方案数目
        for i in range(1, self.ssc_cnt + 1):
            if f[i] > maxf:
                maxf = f[i]
                s = g[i]
            elif f[i] == maxf:
                s = (s + g[i]) % mod
        print(maxf)
        print(s)


if __name__ == "__main__":
    sys.setrecursionlimit(50100)
    Solution().process()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

1175 最大半连通子图(强连通分量) 的相关文章

随机推荐

  • 146. LRU 缓存机制

    class LRUCache class Node public int key public int value public Node prev public Node next public Node public Node int
  • Python经典书籍有哪些?这份书单送给你_黑马程序员

    文章目录 一 Python 基础 01 Python编程 xff1a 从入门到实践 xff08 第2版 xff09 02 Python编程快速上手 xff08 第2版 xff09 03 Python编程初学者指南 04 笨方法 学Pytho
  • 一种 XML2XML 格式之间转换的解决方案(转)

    一种 XML2XML 格式之间转换的解决方案 本文提供了一种 XML2XML 格式之间转换的通用解决方案 我们通过建立用以存储数据信息的数据模型 xff0c 并应用优化器 Optimizers xff0c 转换规则 Rules 对所存储的信
  • 奇异矩阵的解释

    奇异矩阵是线性代数的概念 xff0c 就是该矩阵的秩不是满秩 1 首先 xff0c 看这个矩阵是不是方阵 2 再看此矩阵的行列式 A 是否等于0 xff0c 若等于0 xff0c 称矩阵A为奇异矩阵 3 若不等于0 xff0c 称矩阵A为非
  • 用MATLAB编程求出三位数中全部的水仙花数

    代码 xff1a m 61 100 999 m1 61 rem m 10 求个位数 m2 61 rem fix m 10 10 求十位数 m3 61 fix m 100 求百位数 k 61 find m 61 61 m1 3 43 m2 3
  • MATLAB中的字符串处理

    字符串的处理 在MATLAB中 xff0c 字符串 是用单引号 括起来的字符序列若字符串中的字符含有单引号 xff0c 则该单引号字符 要用两个单引号 来表示在MATLAB中 xff0c 下标索引都是从1开始 案例 xff1a 建立一个字符
  • c语言案例——输入一个字符串,将其逆序输出

    字符串常量可以赋值给一个字符指针 或者一个字符数组 xff0c 比如 xff1a 1 char str 61 this is a string 2 char str2 61 this is a string 3 char str3 100
  • 计算机组成原理——总线控制(总线判优控制、总线通信控制)

    总线控制 一 总线判优控制 1 基本概念 xff1a 总线判优控制 的集中式 方式有三种 xff1a 链式查询 计数器定时查询 独立请求方式 1 链式查询方式 注 xff1a 在查询链中离总线控制器最近的部件具有最高优先权 xff0c 离总
  • os练习题2

    对存放在单一存储设备 如磁带 上的顺序文件连续存取速度快 顺序文件存放在多路存储设备 如磁盘 上时 xff0c 在多道程序的情况下 xff0c 由于别的用户可能驱使磁头移向其它柱面 xff0c 会降低连续存取的速度 顺序文件多用于磁带 从内
  • 编程的基础知识

    c的发展历史 void 类型 程序的入口成了 tmain xff0c 这个和 VC6 0 中的 main 函数类似 xff0c 在前面加个 t xff0c 是为了对 unicode 项目的设置兼容 xff0c 但它们都是程序执行的入口 xf
  • Idea拉取Git上项目的新分支以及在Idea上新建分支提交到Git

    一 Idea拉取Git上项目的新分支 背景说明 xff1a 在Git仓库手动创建了一个新的分支 xff0c 本地Idea想要拉取新建的分支到本地 xff0c 但是Idea分支管理里找不到所建分支 xff0c 所以无法拉取新分支到本地 处理方
  • 如何将eclipse窗口还原

    在我们编程过程中 xff0c 会有各种原因将eclipse窗口摆乱 xff0c 为了方便起见 xff0c 我们可以将窗口还原 xff0c 还原窗口方法如下 xff1a 1 在工具栏依次找到WIndow gt Perspective gt R
  • 图的遍历(python实现)

    深度优先遍历 深度优先遍历 深度优先遍历顾名思义就是一条路走到黑 xff0c 如图所示 xff0c 从0开始遍历 xff0c 遍历他的邻边1 xff0c 当遍历1时 xff0c 发现他的邻边0已经遍历了 xff0c 所以这条路已经走到头了
  • 目前排名前十的编程语言各自的特点和主要应用领域

    一 Python 特点 xff1a 1 简单 xff1a Python是一种代表简单思想的语言 2 易学 xff1a Python有极其简单的语法 3 免费 开源 Python是FLOSS xff08 自由 开放源码软件 xff09 之一
  • mariadb yum 安装之后修改字符集

    1 yum 安装mariadb之后 xff0c 默认不是utf8 的字符集 xff0c 建议在安装完成之后 xff0c 修改成utf8 对已经建立的数据库 xff0c 修改是无效的 xff0c 需要通过其他途径 mariadb 10 2 安
  • Linux(Ubuntu18.04)安装Chrome浏览器

    一 下载deb软件安装包 https dl google com linux deb pool main g google chrome stable google chrome stable 98 0 4758 102 1 amd64 d
  • 配置yum源遇到的问题

    yun配置文件 ambari name 61 local iso baseurl 61 file home redhat iso 填写挂载镜像的位置 enable 61 1 gpgcheck 61 1 gpg签名校验 默认关闭 gpgkey
  • 【Android安全】Android settings命令

    Android 中有一个可执行文件settings xff0c 可以使得调试变得方便 settings用法 xff1a device span class token operator span name span class token
  • PyCharm,Terminal 常用快捷键

    转自 xff1a https blog csdn net sinat 41668302 article details 106211007 PyCharm xff0c Terminal 常用快捷键 enter Terminal 快捷键 功能
  • 1175 最大半连通子图(强连通分量)

    1 问题描述 xff1a 一个有向图 G 61 V xff0c E 称为半连通的 Semi Connected xff0c 如果满足 xff1a u xff0c v V xff0c 满足 u v 或 v u xff0c 即对于图中任意两点