zoukankan      html  css  js  c++  java
  • P6255 ICPC2019 WF Dead-End Detector

    P6255 ICPC2019 WF Dead-End Detector

    想当年这道题被选为校内 ACM 赛前训练,结果是历城二中 57 级全灭,全场三个队,只有有一个队有分,并且只有一道题,非常惨烈,今天重新审视这道题,发现当时水平确实太低。

    题意

    这题题意比较绕,场上对题意也是一知半解,感到迷茫的话可以好好研究样例。

    一个无向图,给死路的起点打标记,死路定义为从起点经过这条边后,无法不掉头走回起点。一条死路如果被一条有标记的死路不掉头地到达,那么这个死路的标记便不必打,求最少的标记数量和标记位置。

    解法

    首先,边双连通分量中的边一定不是死路,所以先缩点,考虑边双构成的树的树边即可。

    我们发现,可以把问题转化为打 Tag 和删 Tag 两步,而这两个阶段是互不干扰的,也就是说我们可以先打 Tag,然后考虑如何删除多余的 Tag 即可。

    说明这个结论,因为死路的定义是确定的,所以无论如何删除 Tag,一条路该是死路仍然是死路,所以打 Tag 删 Tag 互不影响。

    删 Tag

    因为删 Tag 需要的是有打 Tag 的边能不掉头经过这条边。能经过一条边也就是能经过这条边的起点,所以我们对于一个点讨论是否存在有这种边连入即可。

    这个时候可能就会产生问题,就是删除 Tag 之后,原本连入某个点的有 Tag 的边的 Tag 被删除后,这条边的 Tag 就不能被删除了。但是,我们知道能否到达是可以传递的,所以如果有一条边的 Tag 被删除,它删除的 Tag 的正确性不会受到影响,所以我们对每次打 Tag,将终点记录一个 Deleted 标记,最后将有标记的所有点的出边的 Tag 都删掉即可。

    但是有一种情况比较特殊,就是一条入边不能删除自己的回边的标记,这时因为走了入边再走回边就相当于是掉头了,但是如果有多个带 Tag 的入边,就无需考虑这个问题,因为多条入边的回边可以相互被删除。这些情况每个点记录一个唯一带 Tag 入边的地址,特判一下即可。

    void Del(BCC* x) {
      EdgeB* Sid(x->Fst);
      while (Sid) { if (Sid != x->Dont) Sid->Deleted = 1; Sid = Sid->Nxt; }
    }
    

    值得注意的是:不能每次入边连进来就执行这个操作,必须离线处理,因为缩点之后是菊花图的数据可以卡到 (O(n^2)),打标记,最后统一删除可以保证每个点只回遍历一次它的连边。

    打 Tag

    我们发现,一条路径只要能通向一个非节点的双连通分量,在这个双连通分量里绕一圈,就可以不掉头地从原地反向出来。这个结论是接下来打 Tag 的基础。

    对于一个连通块,随便找一个点为根,然后将边分成两类,连向父亲和连向儿子。

    对于连向儿子的边,如果儿子的子树中有非节点的边双,那么不用打 Tag。如果没有,则说明这走条边后没法不掉头回到起点,则给连向儿子的边打 Tag。

    如下是代码:

    void DFSFall(BCC* x) {
      EdgeB* Sid(x->Fst);
      x->SubTree = x->Turn;
      while (Sid) {
        if (Sid->To != x->Fa) {
          DFSFall(Sid->To), x->SubTree |= Sid->To->SubTree;
          if (!(Sid->To->SubTree)) {
            Sid->Ava = 1;
            if (Sid->To->Dont) Sid->To->Dont = EB + 0x3f3f3f3f;
            else Sid->To->Dont = EB + ((Sid - EB) ^ 1);
            Sid->To->Deleted = 1;
          }
        }
        Sid = Sid->Nxt;
      }
    }
    

    然后是儿子连父亲的边,这种边需要讨论不打 Tag 的情况。

    第一种是经过父亲往上走,能走到至少一个非节点边双,对于每个父亲,这种情况可以 DFS 过程中统计。

    第二种情况,注意第二种情况都是建立在前面所说的情况不存在的前提下的。这时父亲存在儿子,它的子树中至少一个非节点边双。

    第二种情况还需要讨论子树存在非节点边双的儿子的数量,当父亲只有一个儿子子树中存在非节点边双,则除了这个儿子以外都可以不打 Tag,但是需要在父亲连向这个特定的儿子的边上打 Tag。当有大于一个儿子的子树中有非节点边双,则按第一种情况处理即可。

    接下来是程序实现:

    void DFSRise(BCC* x) {
      BCC* Son(NULL);
      EdgeB* Sid(x->Fst);
      char More(0);
      while (Sid) {
        if ((Sid->To != x->Fa) && (Sid->To->SubTree)) More = (Son ? 1 : 0), Son = Sid->To;
        Sid = Sid->Nxt;
      }
      Sid = x->Fst;
      if (More || (x->Turn)) {
        while (Sid) {
          if (Sid->To != x->Fa) Sid->To->Turn = 1, DFSRise(Sid->To);
          Sid = Sid->Nxt;
        }
        return;
      }
      if (!Son) {
        while (Sid) {
          if (Sid->To != x->Fa) {
            DFSRise(Sid->To), EB[((Sid - EB) ^ 1)].Ava = 1, x->Deleted = 1;
            if (x->Dont) x->Dont = EB + 0x3f3f3f3f; else x->Dont = Sid;
          }
          Sid = Sid->Nxt;
        }
        return;
      }
      while (Sid) {
        if (Sid->To != x->Fa) {
          if (Sid->To == Son) {
            DFSRise(Sid->To), EB[((Sid - EB) ^ 1)].Ava = 1, x->Deleted = 1;
            if (x->Dont) x->Dont = EB + 0x3f3f3f3f; else x->Dont = Sid;
          }
          else Sid->To->Turn = 1, DFSRise(Sid->To);
        }
        Sid = Sid->Nxt;
      }
    }
    

    代码

    接下来的内容就是人尽皆知的 Tarjan 了,直接缩点即可。

    接下来给出代码省略缺省源的其余部分:

    unsigned m, n, M;
    unsigned A, C, D, t;
    unsigned Cnt(0), Top(0), CntRoot(0), CntPrt(0);
    struct Node;
    struct BCC;
    struct Edge {
      Node* To;
      Edge* Nxt;
    }E[1000005];
    struct EdgeIO {
      unsigned Frm, To;
      const inline char operator <(const EdgeIO& x) const { return (this->Frm ^ x.Frm) ? (this->Frm < x.Frm) : (this->To < x.To); }
    }IO[1000005];
    struct EdgeB {
      BCC* To;
      EdgeB* Nxt;
      EdgeIO UsedTo;
      char Ava, Deleted;
    }EB[1000005];
    struct BCC {
      BCC* Fa;
      EdgeB* Fst, * Dont;
      char Turn, SubTree, Deleted;
    }B[500005], * Root[500005], * CntB(B);
    struct Node {
      Edge* Fst;
      BCC* Bel;
      unsigned DFSr, Low;
    }N[500005], * Stack[500005];
    void Link(Node* x) {
      Edge* Sid(x->Fst);
      while (Sid) {
        if (Sid->To->Bel) {
          if (Sid->To->Bel < x->Bel) {
            EB[Cnt].UsedTo.Frm = x - N, EB[Cnt].UsedTo.To = Sid->To - N;
            EB[Cnt].Nxt = x->Bel->Fst, x->Bel->Fst = EB + Cnt, EB[Cnt++].To = Sid->To->Bel;
            EB[Cnt].UsedTo.Frm = Sid->To - N, EB[Cnt].UsedTo.To = x - N;
            EB[Cnt].Nxt = Sid->To->Bel->Fst, Sid->To->Bel->Fst = EB + Cnt, EB[Cnt++].To = x->Bel;
            Sid->To->Bel->Fa = x->Bel;
          }
        }
        Sid = Sid->Nxt;
      }
    }
    void Shrink(Node* x, Edge* No) {
      x->Low = x->DFSr = ++Cnt, Stack[++Top] = x;
      Edge* Sid(x->Fst);
      while (Sid) {
        if (Sid != No) {
          if (!(Sid->To->DFSr)) Shrink(Sid->To, E + ((Sid - E) ^ 1)), x->Low = min(x->Low, Sid->To->Low);
          else x->Low = min(x->Low, Sid->To->Low);
        }
        Sid = Sid->Nxt;
      }
      if (x->DFSr == x->Low) {
        ++CntB, CntB->Turn = (Stack[Top] != x);
        do { Stack[Top]->Bel = CntB; } while (Stack[Top--] != x);
      }
    }
    signed main() {
      n = RD(), m = RD(), M = (m << 1);
      for (unsigned i(0); i < m; ++i) IO[i].Frm = RD(), IO[i].To = RD();
      sort(IO, IO + m);
      for (unsigned i(0); i < M; i += 2) {
        C = IO[i >> 1].Frm, D = IO[i >> 1].To;
        E[i].Nxt = N[C].Fst, N[C].Fst = E + i, E[i].To = N + D;
        E[i ^ 1].Nxt = N[D].Fst, N[D].Fst = E + (i ^ 1), E[i ^ 1].To = N + C;
      }
      for (unsigned i(1); i <= n; ++i) if (!(N[i].DFSr)) Shrink(N + i, NULL), Root[++CntRoot] = CntB;
      Cnt = 0;
      for (unsigned i(1); i <= n; ++i) Link(N + i);
      for (unsigned i(1); i <= CntRoot; ++i) DFSFall(Root[i]), DFSRise(Root[i]);
      for (BCC* i(B + 1); i <= CntB; ++i) if (i->Deleted) Del(i);
      for (unsigned i(0); i < Cnt; ++i) if (EB[i].Ava && (!EB[i].Deleted)) IO[++CntPrt] = EB[i].UsedTo;
      sort(IO + 1, IO + CntPrt + 1);
      printf("%u
    ", CntPrt);
      for (unsigned i(1); i <= CntPrt; ++i) printf("%u %u
    ", IO[i].Frm, IO[i].To);
      system("pause");
      return Wild_Donkey;
    }
    
  • 相关阅读:
    ubuntun16.04不支持intel的最新网卡,升级到17.10后解决
    python网络爬虫之使用scrapy下载文件
    Django之QuerySet 创建对象
    一起来学linux:磁盘与文件系统:
    python自动化运维九:Playbook
    一起来学linux:FTP服务器搭建
    Learning Scrapy 中文版翻译 第二章
    python自动化运维八:Ansible
    linux c编程:make编译一
    【原创】大叔经验分享(27)linux服务器升级glibc故障恢复
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/15329215.html
Copyright © 2011-2022 走看看