拓扑排序



更新(20180727)

首先打脸,更新了更新了
修改一下以前写的比较渣的代码
改完了,发现怎么4个月前写的代码这么辣鸡…

前言

做了些题目后发现呢,这个拓扑排序用于解决有先后次序的问题,而且特别实用!即使是在日常生活也感觉它特别的实用!
另外关于拓扑排序的计数问题,有时间啃完 + 啃得下就再发新的文

  1. 拓扑排序 —— 百度百科
    https://baike.baidu.com/item/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F
  2. 拓扑排序
    http://blog.csdn.net/qq_35644234/article/details/60578189

不会做的题

  1. Frame Stacking | POJ 1128
  2. Permutation | HDU 4917


Genealogical tree | POJ-2367

题目大意
有N个人,他们知道他们的后代有谁,以及他们各自的编号,求他们从长辈到晚辈顺序的编号序列
思路
模板题来着的

  //[POJ - 2367]
  #include <cstdio>
  #include <cstring>
  #include <queue>
  #include <vector>
  using namespace std;
  const int N = 1e4 + 15;

  struct edge{
      int v, val, next;
      edge(int pv, int pval, int pnext): v(pv), val(pval), next(pnext) {}
      edge() {}
  };

  edge e[N];          //e[i].next记录e[i]的相邻边为第几条边
  int head[N];        //head[i]记录以i为出发点的最后一条边是第几条边
  int tot;            //边的总数
  int idg[N];         //入度
  queue<int> que;
  vector<int> ans;

  void addedge(int u, int v, int val){
      e[tot] = edge(v, val, head[u]);
      head[u] = tot++;
      idg[v]++;
  }
  //tot : 函数完成时代表总共几条边,函数执行时代表是第几条边
  //e[tot].next : 记录上一次从from出发的边是第几条边,即e[tot]的邻近边,因此可用此遍历从from出发的所有边,到-1时遍历完成
  //head[u] = tot : 更新这次从from出发的边是第tot边

  void init(){
      memset(head, -1, sizeof(head));
      memset(idg, 0, sizeof(idg));
      while(!que.empty())     que.pop();
      ans.clear();
      tot = 0;
  }

  void topoSort(int n){
      for(int i = 1; i <= n; i++){
          if(!idg[i])    que.push(i);
      }
      while(!que.empty()){
          int u = que.front();
          que.pop();
          ans.push_back(u);
          for(int i = head[u]; ~i; i = e[i].next){
              int v = e[i].v;
              if((--idg[v]) == 0)    que.push(v);
          }
      }
  }

  int main(){
      int n;
      while(~scanf("%d", &n)){
          init();
          for(int i = 1; i <= n; i++){
              int v;
              while(scanf("%d", &v) && v)  addedge(i, v, 0);
          }
          topoSort(n);
          for(int i = 0; i < ans.size(); i++){
              printf("%d", ans[i]);
              if(i < ans.size() - 1)  putchar(' ');
          }
      }
      return 0;
  }


Sorting It All Out | POJ 1094

题目大意
给定n个字母之间的m条关系,判断能否确定唯一序列,第i条关系能判断则输出结果,第i条关系后矛盾则输出在第i条关系时矛盾,不能确定唯一则输出不能确定
思路
其实这题我查了一点点的题解,因为比较懵逼如何输出第几条关系,结果题解给出的方法是:每加入一条就进行一次topoSort……有点暴力呀这方法……
矛盾就是图中有环,topoSort结束后仍然有入度不为0的点
不能确定就是在最后一条关系式进行topoSort时,存在某次队列中的元素超过1个的情况

//[POJ-1094]
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 15;
const int inf = 0x3f3f3f3f;

struct edge{
    int v, next;
    edge(int pv, int pnext): v(pv), next(pnext) {}
    edge() {}
};

edge e[N];
int head[N], idg[N], idg_cpy[N];
int tot;
queue<int> que;

void init(){
    memset(head, -1, sizeof(head));
    memset(idg_cpy, 0, sizeof(idg_cpy));
    tot = 0;
}

void addedge(int u, int v){
    e[tot] = edge(v, head[u]);
    head[u] = tot++;
    idg_cpy[v]++;
}

int topoSort(int n, string& ans){
    ans.clear();
    int flag = 0;                           //初始为确定顺序状态
    int sum = 0;
    memcpy(idg, idg_cpy, sizeof(idg_cpy));
    for(int i = 0; i < n; i++){
        if(!idg[i])    que.push(i);
    }
    while(!que.empty()){
        if(que.size() > 1)      flag = 1;   //不能确定,某次队列中的元素超过1个
        int u = que.front();
        que.pop();
        sum++;
        ans.push_back(u + 'A');
        for(int i = head[u]; ~i; i = e[i].next){
            int v = e[i].v;
            if((--idg[v]) == 0)   que.push(v);
        }
    }

    if(sum != n)    flag = -1;      //有环
    return flag;
}

int main(){
    int n, m;
    while(~scanf("%d%d", &n, &m) && n + m){
        init();
        string ans;
        int s = inf, stat = 1;
        for(int i = 1; i <= m; i++){
            char str[4];
            scanf("%s", str);

            //已经确定顺序或矛盾就不用再进行topoSort了,但是要读完
            if(stat == -1 || stat == 0)  continue;

            int u = str[0] - 'A', v = str[2] - 'A';
            addedge(u, v);
            stat = topoSort(n, ans);
            if(stat == 0 || stat == -1)   s = i;
        }
        if(stat == -1)      printf("Inconsistency found after %d relations.\n", s);
        else if(stat == 1)  printf("Sorted sequence cannot be determined.\n");
        else                printf("Sorted sequence determined after %d relations: %s.\n", s, ans.c_str());
    }
    return 0;
}


Rank of Tetris | HDU 1811

题目大意
中文的就不解释了(•̀ᴗ•́)و ̑̑
思路
和普通的topoSort相比,最大的区别就是有 = 号,而topoSort本身是给出先后顺序序列,没办法(或者我不知道)来解决 = 号的问题,因此可以引入 并查集 来解决
先将<和>的关系存起来,遇到 = 号就合并,读入完后再加边,然后就像普通的topoSort一样做就行

  //[HDU-1811]
  #include <cstdio>
  #include <cstring>
  #include <queue>
  using namespace std;
  const int N = 2e4 + 15;

  struct edge{
      int v, next;
      edge(int pv, int pnext): v(pv), next(pnext) {}
      edge() {}
  };

  edge e[N];
  int head[N], idg[N];
  int tot;
  int ft[N];
  queue<int> que;
  int rel[N][2];

  void init(int n){
      memset(head, -1, sizeof(head));
      memset(idg, 0, sizeof(idg));
      tot = 0;
      for(int i = 0; i < n; i++)  ft[i] = i;
  }

  int find(int x) { return (ft[x] == x ? x : ft[x] = find(ft[x])); }

  void merge(int x, int y){
      int p = find(x), q = find(y);
      if(p != q) ft[q] = p;
  }

  void addEdge(int u, int v){
      e[tot] = edge(v, head[u]);
      head[u] = tot++;
      idg[v]++;
  }

  void topoSort(int n){
      int flag = 0;       //0:OK, 1:UNCERTAIN; -1:CONFLICT
      int sum = 0, m = 0;
      for(int i = 0; i < n; i++){
          if(ft[i] != i)       continue;     //注意排除掉不为本身的点,因为他们都归到父节点去了
          if(!idg[i])     que.push(i);
          m++;
      }
      while(!que.empty()){
          if(que.size() > 1)      flag = 1;
          int u = que.front();
          que.pop();
          sum++;
          for(int i = head[u]; ~i; i = e[i].next){
              int v = e[i].v;
              if((--idg[v]) == 0)  que.push(v);
          }
      }

      if(sum != m)            puts("CONFLICT");
      else if(flag ==  0)     puts("OK");
      else                    puts("UNCERTAIN");
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          init(n);
          int t = 0;
          for(int i = 0; i < m; i++){
              int a, b;
              char op;
              scanf("%d %c %d", &a, &op, &b);
              if(op == '=')       merge(a, b);    // 合并 =
              else{
                  if(op == '>')   swap(a, b);
                  rel[t][0] = a, rel[t][1] = b;   // > 转 <,都存起来
                  t++;
              }
          }
          for(int i = 0; i < t; i++){
              int u = find(rel[i][0]), v = find(rel[i][1]);     // 加边是用父节点加
              addEdge(u, v);
          }
          topoSort(n);
      }
      return 0;
  }


-0你电脑炸啦 | SCU 4521

题目大意
照样不解释,懒~
思路
第一次看到这道题还是几个月前,现在看这题就是送分的嘛
有先后次序问题,所以可以topoSort
每次都扫描一个2x2的面积,看看除本身值以外还有哪些值,加边,然后topoSort能完成就OK,完成不了就GG

  //[SCU-4521]
  #include <cstdio>
  #include <cmath>
  #include <climits>
  #include <cstring>
  #include <iostream>
  #include <queue>
  #include <vector>
  using namespace std;
  const int N = 2e4 + 15;

  struct edge{
      int v, next;
      edge(int pv, int pnext): v(pv), next(pnext) {}
      edge() {}
  };

  edge e[N];
  int head[N];
  int tot;
  int idg[N];
  int G[5][5];
  queue<int> que;

  const int dx[] = {0, 1, 1, 0};    //本身,本身右方、下方和右下方(不按实际代码顺序)
  const int dy[] = {0, 0, 1, 1};

  void init(){
      memset(head, -1, sizeof(head));
      memset(idg, 0, sizeof(idg));
      tot = 0;
  }

  void addEdge(int u, int v){
      e[tot] = edge(v, head[u]);
      head[u] = tot++;
      idg[v]++;
  }

  bool topoSort(int n){
      int sum = 0;
      for(int i = 1; i <= n; i++){
          if(!idg[i])     que.push(i);
      }
      while(!que.empty()){
          int u = que.front();
          que.pop();
          sum++;
          for(int i = head[u]; ~i; i = e[i].next){
              int v = e[i].v;
              if((--idg[v]) == 0){
                  que.push(v);
              }
          }
      }
      return sum == n;
  }

  int main(){
      int t;
      scanf("%d", &t);
      while(t--){
          init();
          for(int i = 1; i <= 4; i++){
              for(int j = 1; j <= 4; j++){
                  scanf("%d", &G[i][j]);
              }
          }
          for(int i = 1; i <= 3; i++){
              for(int j = 1; j <= 3; j++){
                  int now = (i - 1) * 3 + j;    //本来这块地方应该出现的值  
                  for(int k = 0; k < 4; k++){
                      if(G[i + dx[k]][j + dy[k]] != now){   //有别的窗口堆叠在上面,即出现先后次序  
                          addEdge(now, G[i + dx[k]][j + dy[k]]);
                      }
                  }
              }
          }
          printf("%s\n", topoSort(9) ? "Lucky dog!" : "BOOM!");
      }
      return 0;
  }