BZOJ1093 [ZJOI2007]最大半连通子图

Address

Description

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

Input

  • 第一行包含两个整数 N,M,X N,M 分别表示图 G 的点数与边数,X的意义如上文所述
  • 接下来 M 行,每行两个正整数a,b,表示一条有向边 (a,b) 。图中的每个点将编号为 1,2,3N ,保证输入中同一个 (a,b) 不会出现两次
  • N100000,M1000000,X108

Output

  • 应包含两行,第一行包含一个整数 K ,第二行包含整数C_mod_X

Sample Input

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

Sample Output

  • 3
    3

Solution

  • 对问题进行转换。
  • 如果原图的某一部分是一个强连通分量,那么这一部分显然是半连通子图。
  • 如果原图的某一部分是一条链,那么这一部分显然也是半连通子图。
  • 因此我们先用Tarjan缩点,新图是一个有向无环图,然后问题就变为求新图的最长链长度及个数。
  • 显然要用拓扑序来做DP,设 f[x] 表示到第 x 个强连通分量的最长链个数,g[x]表示长度,记 num[x] x 个强连通分量的点数。
  • 则:
    • g[x]+num[y]=g[y]f[y]+=f[x]
    • g[x]+num[y]>g[y]f[y]=f[x],g[y]=g[x]+num[y]
    • 注意缩点以后不能建多余的边,因此在拓扑排序前要将所有边排序后去重。
    • 本题的瓶颈在于边的排序,时间复杂度 O(mlogm) ,期望得分100分。
    • Code

      #include <iostream>
      #include <cstdio>
      #include <cctype>
      #include <algorithm>
      #include <cstring>
      
      using namespace std;
      
      namespace INOUT
      {
          const int S = 1 << 20;
          char frd[S], *hed = frd + S;
          const char *tal = hed;
      
          inline char nxtChar()
          {
              if (hed == tal)
                  fread(frd, 1, S, stdin), hed = frd;
              return *hed++;
          }
      
          inline int get()
          {
              char ch; int res = 0; bool flag = false;
              while (!isdigit(ch = nxtChar()) && ch != '-');
              (ch == '-' ? flag = true : res = ch ^ 48);
              while (isdigit(ch = nxtChar()))
                  res = res * 10 + ch - 48;
              return flag ? -res : res;
          }
      
          inline void put(int x)
          {
              if (x > 9) put(x / 10);
              putchar(x % 10 + 48);
          }
      };
      using namespace INOUT;
      
      const int N = 1e5 + 5, M = 1e6 + 5;
      int n, m, Xmod, C, top, tis, E; 
      int dfn[N], low[N], stk[N], col[N], num[N];
      int rin[N], rout[N], f[N], g[N]; bool inv[N];
      
      struct Link
      {
          int l, r;
      
          friend inline bool operator < (const Link &x, const Link &y)
          {
              return x.l < y.l || x.l == y.l && x.r < y.r;
          }
      }a[M];
      
      struct Edge
      {
          int to; Edge *nxt;
      };
      
      Edge p[M], *T = p, *lst[N];
      Edge q[M], *Q = q, *rst[N];
      
      inline void RinkEdge(int x, int y)
      {
          (++Q)->nxt = rst[x]; rst[x] = Q; Q->to = y;
          ++rin[y]; ++rout[x];
      }
      inline void LinkEdge(int x, int y)
      {
          (++T)->nxt = lst[x]; lst[x] = T; T->to = y; 
      }
      inline void CkMin(int &x, int y) {if (x > y) x = y;}
      inline void Swap(int &x, int &y) {int t = x; x = y; y = t;}
      
      inline void Tarjan(int x)
      {
          inv[x] = true; stk[++top] = x;
          dfn[x] = low[x] = ++tis; int y;
          for (Edge *e = lst[x]; e; e = e->nxt)
              if (!dfn[y = e->to])
                  Tarjan(y), CkMin(low[x], low[y]);
              else if (inv[y])
                  CkMin(low[x], dfn[y]);
          if (dfn[x] == low[x])
          {
              inv[x] = false; num[col[x] = ++C] = 1;
              while (y = stk[top--], y != x)
                  inv[y] = false, ++num[col[y] = C];
          }
      }
      
      int main()
      {
          //freopen("semi.in", "r", stdin);
          //freopen("semi.out", "w", stdout);
          n = get(); m = get(); Xmod = get(); int x, y;
          for (int i = 1; i <= m; ++i)
          {
              x = get(); y = get();
              LinkEdge(x, y);
          }
      
          for (int i = 1; i <= n; ++i)
              if (!dfn[i]) Tarjan(i);
          for (int i = 1; i <= n; ++i)
              for (Edge *e = lst[i]; e; e = e->nxt)
                  if (col[i] != col[y = e->to])
                      a[++E].l = col[i], a[E].r = col[y]; 
      
          sort(a + 1, a + E + 1);
          RinkEdge(a[1].l, a[1].r);
          for (int i = 2; i <= E; ++i)
              if (a[i].l == a[i - 1].l && a[i].r == a[i - 1].r)
                  continue;
              else RinkEdge(a[i].l, a[i].r);
          ++C;
          for (int i = 1; i < C; ++i)
              if (!rout[i]) RinkEdge(i, C);
      
          for (int i = 1; i <= C; ++i)
              if (!rin[i]) stk[++top] = i;
          for (int i = 1; i <= C; ++i)
              f[i] = 1, g[i] = num[i];
      
          while (top)
          {
              x = stk[top--];
              for (Edge *e = rst[x]; e; e = e->nxt)
              {
                  y = e->to;
                  if (g[x] + num[y] > g[y]) 
                      g[y] = g[x] + num[y], f[y] = 0;
                  if (g[x] + num[y] == g[y]) 
                      (f[y] += f[x]) %= Xmod;
                  if (!--rin[y]) stk[++top] = y;
              }
          }
          printf("%d\n%d", g[C], f[C]);
          return 0;
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值