• 图论 从入门到____________


    图论

    特殊图:

    • 没有环的无向图 : 树(图连通) 森林(不连通)

    • (有向树egin{cases} 外向树 o 根节点向外延伸\ 内向树 o 从叶子节点指向根 end{cases})

    • 章鱼图(基环树) : 无向联通,内有一个环 ( o) 断环成树

    • (仙人掌egin{cases} 边仙人掌 o 每条边最多在一个环上\ 点仙人掌 o 每个点最多在一个环上 \ 点仙人掌 一定是边仙人掌 \ end{cases})

    • 二分图 : 树也为二分图,按深度奇偶分开即可

    判断二分图的方法: 黑白染色法(不存在环)

    充要条件 : 二分图一定没有奇环

    最短路

    • 单源最短路 : 一点到多点
    • 多源最短路 ;多点到多点
    • 差分约束

    (x_i - x_j >= c)

    (Rightarrow{x_j - x_i <= -c})

    (Rightarrow{add(i,j,-c)})


    (x_i - x_j <= c)

    (Rightarrow{x_i - x_j <= c})

    (Rightarrow{add(j,i,c)})


    (x_i = x_j)

    (Rightarrow{x_i - x_j <= 0,~~~ x_j - x_i <= 0})

    (Rightarrow{add(j,i,0),~~~ add(i,j,0)})


    (x_?)移到同一边,后面的未知数的下标向前面未知数的下标连边,边权为不等式后的值

    生成树

    • prim板子
    void prim() {
      q.push((node){s,0});
      while(!q.empty()) {
        node tp = q.fr;
        if(vis[tp.po]) continue;
        vis[tp.po] = true;
        q.pop() ;
        for(int i = head[tp.po] ;i ; i = e[i].net) {
          int to = e[i].to;
          if(dis[to] > e[i].dis) {
            dis[to] = e[i].dis;
            if(!vis[to]) q.push((node){to,e[i].dis});
           }
        }
    
      }
    }
    
    • 并查集?

    • 路径压缩

    int findf(int x) {
      return f[x] == x ? f[x] = findf(f[x]);
    }
    
    • 按置合并
    void hb(int xx ,int yy){
      int x = findf(xx) ,y = fidnf(yy);
      if(dep[x] > dep[y]) 
        f[y] = x;
      else  f[x] = y;
      if(dep[x] == dep[y]) dep[y]++;
    }
    

    严格次小生成树

    1. 首先可以发现次小生成具有一定的性质:
      我们如果要构造次小生成树,我们就要,尽可能的删去树边中的一个最大值,
      然后维护一个非树边中的最小值,这个最小值要比树边在最大值大,但是比树边的最大值要大
    2. 怎样做到(nlog(n))的的时间复杂度? -- 倍增即可
    3. 我们加入一条非树边使得这棵树出现了一个环,所以,我们要断环
    4. 可以预先 DFS 处理这棵树的点深度,每个点的父亲,最大值和次大值的初始值
      再使用倍增求出每个点所能到达的最大值和次大值
      然后找出一颗最小生成树,统计它的权值和,并对于每条处于最小生成树中的边打一个标记
      对于每条不在最小生成树中的边,找出与它相对的处于最小生成树中的最大的一条边,删去,然后一边跳一边枚举路径上的边权
      然后判断当前枚举边与删去边的权值大小,若相同加入当前边的次大值,否则加入最大值,然后统计权值
      最后把统计出的所有权值取一个最小值就是严格次小生成树的权值
    #include <algorithm>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <string>
    #define int long long
    using namespace std;
    const int N = 1e5 + 100;
    const long long inf = 21474836470000;
    int read() {
      int s = 0, f = 0;
      char ch = getchar();
      while (!isdigit(ch))
        f |= (ch == '-'), ch = getchar();
      while (isdigit(ch))
        s = s * 10 + (ch ^ 48), ch = getchar();
      return f ? -s : s;
    }
    int n, sum,m;
    bool used[N];
    namespace SLV {
      int maxx[N][25], slv[N][25];
      struct Edge {
        int from, to, dis, net;
        bool operator<(const Edge& b) const { return dis < b.dis; }
      } e[N << 2];
      int head[N], nume;
      void add_edge(int from, int to, int dis) {
        e[++nume].from = from, e[nume].to = to, e[nume].dis = dis;
        e[nume].net = head[from], head[from] = nume;
      }
      int fa[N][25], dep[N];
      void dfs(int x, int fath) {
        fa[x][0] = fath, dep[x] = dep[fath] + 1ll;
        for (int i = head[x]; i; i = e[i].net) {
          int to = e[i].to;
          if (to == fath)
            continue;
          maxx[to][0] = e[i].dis;
          slv[to][0] = -inf;
          dfs(to, x);
        }
      }
      void pre() {
        for (int i = 1; i <= 18; ++i) {
          for (int j = 1; j <= n; ++j) {
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
            maxx[j][i] = max(maxx[j][i - 1], maxx[fa[j][i - 1]][i - 1]);
            slv[j][i] = max(slv[j][i - 1], slv[fa[j][i - 1]][i - 1]);
            if (maxx[j][i - 1] > maxx[fa[j][i - 1]][i - 1])
              slv[j][i] = max(slv[j][i], maxx[fa[j][i - 1]][i - 1]);
            else if (maxx[j][i - 1] < maxx[fa[j][i - 1]][i - 1])
              slv[j][i] = max(slv[j][i], maxx[j][i - 1]);
          }
        }
      }
      int lca(int x, int y) {
        if (dep[x] < dep[y])
          swap(x, y);
        for (int i = 18; i >= 0; i--) {
          if (dep[fa[x][i]] >= dep[y])
            x = fa[x][i];
        }
        if (x == y)
          return x;
        for (int i = 18; i >= 0; i--) 
          if (fa[x][i] != fa[y][i])
            x = fa[x][i], y = fa[y][i];
        return fa[x][0];
      }
      int q_max(int x, int y, int dis) {
        int ans = -inf;
        for (int i = 18; i >= 0; i--) {
          if (dep[fa[x][i]] >= dep[y]) {
            if (dis != maxx[x][i])
              ans = max(ans, maxx[x][i]);
            else
              ans = max(ans, slv[x][i]);
            x = fa[x][i];
          }
        }
        return ans;
      }
    } 
    
    namespace Kur {
      int nume, head[N];
      struct Edge {
        int from, to, dis, net;
        bool operator<(const Edge& b) const { return dis < b.dis; }
      } e[N << 2];
      void add_edge(int from, int to, int dis) {
        e[++nume].from = from, e[nume].to = to, e[nume].dis = dis;
        e[nume].net = head[from], head[from] = nume;
      }
      int f[N];
      int findf(int x) {
        return f[x] == x ? x : f[x] = findf(f[x]);
      }
      void kur() {
        for (int i = 1; i <= n; ++i)  f[i] = i;
        sort(e + 1, e + nume + 1);
        for (int i = 1; i <= nume; ++i) {
          int x = findf(e[i].from), y = findf(e[i].to);
          if (x != y) {
            f[x] = y;
            sum += e[i].dis;
            SLV::add_edge(e[i].from, e[i].to, e[i].dis);
            SLV::add_edge(e[i].to, e[i].from, e[i].dis);
            used[i] = 1;
          }
        }
      }
      int solve() {
        int ans = inf;
        for (int i = 1; i <= nume; i++) {
            if (used[i])  continue;
            int Lca = SLV::lca(e[i].from, e[i].to);
            int maxfrom = SLV::q_max(e[i].from, Lca, e[i].dis);
            int maxto = SLV::q_max(e[i].to, Lca, e[i].dis);
            ans = min(ans, sum - max(maxfrom, maxto) + e[i].dis);
          }
          return ans;
      }
    }
    signed main() {
      n = read(), m = read();
      for (int i = 1, u, v, w; i <= m; ++i) {
        u = read(), v = read(), w = read();
        Kur::add_edge(u, v, w);
      }
      Kur::kur();
      SLV::slv[1][0] = -inf;
      SLV::dfs(1, 0);
      SLV::pre();
      printf("%lld",Kur::solve());
      system("pause");
      return 0;
    }
    

    CF 733 F

    solution:

    考虑要求的最小生成树与原图的最小生成树的关系,可以发现,这与次小生成树类似,同样可能考虑加边断环的问题,只不过要考虑的问题更多,

    分情况讨论

    1. 减小的边为树边,那就找(c)最小的那条边,反正可以变为负边,一个劲的减就行
    2. 减小的边为非树边,那就类似于加边断环求解,判断加这条边和加到树边哪种情况更优秀
    #include <algorithm>
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <string>
    #include <vector>
    #define int long long
    
    using namespace std;
    const int N = 2e5 + 100;
    int read() {
      int s = 0, f = 0;
      char ch = getchar();
      while (!isdigit(ch))
        f |= (ch == '-'), ch = getchar();
      while (isdigit(ch))
        s = s * 10 + (ch ^ 48), ch = getchar();
      return f ? -s : s;
    }
    int n, m;
    int val, s, pre_jia, pre_jian, flag;
    bool vis[N], vis_[N];
    int w[N], c[N];
    struct Edge {
      int from, to, dis, c, id, net;
    } a[N << 1], e[N << 1];
    struct ANS {
      int id, dis;
    } ans[N][30];
    int Head[N], numa;
    void add(int from, int to, int dis, int c, int id) {
      a[++numa] = (Edge){from, to, dis, c, id, Head[from]};
      Head[from] = numa;
    }
    int head[N], nume;
    void add_edge(int from, int to, int dis, int c, int id) {
      e[++nume] = (Edge){from, to, dis, c, id, head[from]};
      head[from] = nume;
    }
    int fath[N][30], dep[N];
    void dfs(int x, int fa) {
      fath[x][0] = fa;
      // cout << x << " " << fa << endl;
      for (int i = head[x]; i; i = e[i].net) {
        int to = e[i].to;
        if (to == fa)
          continue;
        dep[to] = dep[x] + 1ll;
        ans[to][0].dis = e[i].dis;
        ans[to][0].id = e[i].id;
        dfs(to, x);
      }
    }
    void pre() {
      for (int i = 1; i <= 26; i++) {
        for (int j = 1; j <= n; j++) {
          // cout<< fath[j][0]<<endl;
          fath[j][i] = fath[fath[j][i - 1]][i - 1];
          if (ans[j][i - 1].dis > ans[fath[j][i - 1]][i - 1].dis) {
            ans[j][i].dis = ans[j][i - 1].dis;
            ans[j][i].id = ans[j][i - 1].id;
          } else {
            ans[j][i].dis = ans[fath[j][i - 1]][i - 1].dis;
            ans[j][i].id = ans[fath[j][i - 1]][i - 1].id;
          }
        }
      }
    }
    int LCA(int x, int y) {
      if (dep[x] > dep[y])
        swap(x, y);
      for (int i = 26; i >= 0; --i) {
        if (dep[x] > dep[fath[y][i]])
          continue;
        y = fath[y][i];
      }
      if (x == y)
        return x;
      for (int i = 26; i >= 0; i--) {
        if (fath[x][i] != fath[y][i])
          x = fath[x][i], y = fath[y][i];
      }
      return fath[x][0];
    }
    ANS q_max(int x, int y) {
      ANS Ans;
      Ans.dis = -1e9 - 100;
      for (int i = 26; i >= 0; --i) {
        if (dep[fath[x][i]] >= dep[y]) {
          if (Ans.dis < ans[x][i].dis) {
            Ans.dis = ans[x][i].dis;
            Ans.id = ans[x][i].id;
          }
          x = fath[x][i];
        }
      }
      return Ans;
    }
    int f[N];
    int findf(int x) {
      return f[x] == x ? x : f[x] = findf(f[x]);
    }
    
    bool cmp(Edge a, Edge b) {
      return a.dis < b.dis;
    }
    void kur() {
      for (int i = 1; i <= n; i++)
        f[i] = i;
      sort(a + 1, a + m + 1, cmp);
      for (int i = 1; i <= m; i++) {
        int x = findf(a[i].from), y = findf(a[i].to);
        if (x == y)
          continue;
        add_edge(a[i].from, a[i].to, a[i].dis, a[i].c, i);
        add_edge(a[i].to, a[i].from, a[i].dis, a[i].c, i);
        f[x] = y;
        val += a[i].dis;
        vis[i] = true;
        vis_[i] = true;
      }
    }
    int temp = 1e18 + 100;
    void END() {
      for (int i = 1; i <= m; i++) {
        int val_ = s / a[i].c;
        if (vis[i]) {
          if (temp > val - val_) {
            flag = i;
          }
          temp = min(temp, val - val_);
        } else {
          int lca = LCA(a[i].from, a[i].to);
          // cout << a[i].from << " " << a[i].to << endl;
          // cout << lca << ' ';
          ANS maxu = q_max(a[i].from, lca);
          ANS maxv = q_max(a[i].to, lca);
          if (maxu.dis > maxv.dis) {
            if (temp > val - maxu.dis + a[i].dis - val_) {
              temp = val - maxu.dis + a[i].dis - val_;
              vis_[pre_jia] = false;
              vis_[pre_jian] = true;
              vis_[i] = true;
              vis_[maxu.id] = false;
              pre_jia = i;
              pre_jian = maxu.id;
              flag = i;
            }
          } else {
            if (temp > val - maxv.dis + a[i].dis - val_) {
              temp = val - maxv.dis + a[i].dis - val_;
              vis_[pre_jia] = false;
              vis_[pre_jian] = true;
              vis_[i] = true;
              vis_[maxv.id] = false;
              pre_jia = i;
              pre_jian = maxv.id;
              flag = i;
            }
          }
        }
      }
    }
    signed main() {
      n = read(), m = read();
      for (int i = 1; i <= m; i++)
        w[i] = read();
      for (int i = 1; i <= m; i++)
        c[i] = read();
      for (int i = 1, u, v; i <= m; i++) {
        u = read(), v = read();
        add(u, v, w[i], c[i], i);
      }
      s = read();
      kur();
      dep[1] = 1;
      ans[1][0].dis = e[1].dis;
      ans[1][0].id = e[1].id;
      dfs(1, 1);
      pre();
      END();
      if (flag) {
        a[flag].dis = a[flag].dis - (s / a[flag].c);
      }
      printf("%lld
    ", temp);
      for (int i = 1; i <= m; i++) {
        if (vis_[i]) {
          // cout << i<< ' ';
          printf("%lld %lld
    ", a[i].id, a[i].dis);
        }
      }
      // system("pause");
      return 0;
    }
    

    强连通分量

    2- SAT

    和平委员会

    • 两个点 (x_i & x_j = false)
    • ( ightarrow add(false(x_i),x_j) ,add(false(x_j), x_i))
    • 然后跑强连通分量即可,如果(x 和 false(x))在同一个强连通分量中,就无解
    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <queue>
    #include <string>
    #define ll long long
    using namespace std;
    const int N = 5e5 + 100;
    const int inf = 1e9;
    int read() {
      int s = 0, f = 0;
      char ch = getchar();
      while (!isdigit(ch))
        f |= (ch == '-'), ch = getchar();
      while (isdigit(ch))
        s = s * 10 + (ch ^ 48), ch = getchar();
      return f ? -s : s;
    }
    struct Edge {
      int from, to, net;
    } e[N << 1];
    int head[N], nume;
    void add_edge(int from, int to) {
      e[++nume] = (Edge){from, to, head[from]};
      head[from] = nume;
    }
    int st[N], sn, vis[N];
    int dfn[N], low[N], cnt;
    int scc, rd[N], cd[N], sum[N];
    int num[N];
    int n, m, s, p;
    int start;
    void tir(int x) {
      st[++sn] = x, vis[x] = 1;
      low[x] = dfn[x] = ++cnt;
      for (int i = head[x]; i; i = e[i].net) {
        int to = e[i].to;
        if (!dfn[to])
          tir(to), low[x] = min(low[x], low[to]);
        else if (vis[to])
          low[x] = min(low[x], dfn[to]);
      }
      if (dfn[x] == low[x]) {
        int top = st[sn--];
        vis[top] = 0;  scc++;
        num[top] = scc;
        while (top != x) {
          top = st[sn--];
          vis[top] = 0;
          num[top] = scc;
        }
      }
    }
    int fr(int x) {
      return (x & 1) ? (x + 1) : (x - 1);
    }
    int main() {
      n = read(), m = read();
      for (int i = 1; i <= m; i++) {
        int u = read(), v = read();
        add_edge(u, fr(v));
        add_edge(v, fr(u));
      }
      for (int i = 1; i <= 2 * n; i++) {
        if (!dfn[i])
          tir(i);
      }
      for (int i = 1; i <= 2 * n; i++) {
        if ((i & 1) && num[i] == num[i + 1]) {
          puts("NIE");
          system("pause");
          return 0;
        }
      }
      for (int i = 1; i <= 2 * n; i++) {
        if (num[i] < num[fr(i)])
          printf("%d
    ", i);
      }
      system("pause");
      return 0;
    }
    

    模板

    虽然写着模板但是确实比上面那个题难一点,建图的时候要弄清楚逻辑关系

    题目要求的是两者中满足一个即可
    所以:

    每一建边的时候我们(false o true) 建边即可

    我们令哪一个条件取反,就用这个相反的条件向未更改的条件连边,因为这里的(false 和true)是相对而言的,并非是单纯的(0 o 1)建边而是条件不满足的向条件满足的建边

    举个小样例:

    要求满足的是(a = 0 , b = 0),那么我们就对其中一个取反(lnot a o b)(lnot b o a)都建一条边以此类推即可

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <queue>
    #include <string>
    #define ll long long
    using namespace std;
    const int N = 2e6 + 100;
    const int inf = 1e9;
    int read() {
      int s = 0, f = 0;
      char ch = getchar();
      while (!isdigit(ch))
        f |= (ch == '-'), ch = getchar();
      while (isdigit(ch))
        s = s * 10 + (ch ^ 48), ch = getchar();
      return f ? -s : s;
    }
    struct Edge {
      int from, to, net;
    } e[N << 1];
    int head[N], nume;
    void add_edge(int from, int to) {
      e[++nume] = (Edge){from, to, head[from]};
      head[from] = nume;
    }
    int st[N], sn, vis[N];
    int dfn[N], low[N], cnt;
    int scc, rd[N], cd[N], sum[N];
    int num[N];
    int n, m, s, p;
    int start;
    void tir(int x) {
      st[++sn] = x, vis[x] = 1;
      low[x] = dfn[x] = ++cnt;
      for (int i = head[x]; i; i = e[i].net) {
        int to = e[i].to;
        if (!dfn[to])
          tir(to), low[x] = min(low[x], low[to]);
        else if (vis[to])
          low[x] = min(low[x], dfn[to]);
      }
      if (dfn[x] == low[x]) {
        int top = st[sn--];
        vis[top] = 0;
        scc++;
        num[top] = scc;
        while (top != x) {
          top = st[sn--];
          vis[top] = 0;
          num[top] = scc;
        }
      }
    }
    
    int main() {
      n = read(), m = read();
      for (int i = 1; i <= m; i++) {
        int x = read(), a = read(), y = read(), b = read();
        if (a && b)
          add_edge(x + n, y), add_edge(y + n, x);
        if (!a && b)
          add_edge(x, y), add_edge(y + n, x + n);
        if (a && !b)
          add_edge(x + n, y + n), add_edge(y, x);
        if (!a && !b)
          add_edge(x, y + n), add_edge(y, x + n);
      }
      for (int i = 1; i <= 2 * n; i++) {
        if (!dfn[i])
          tir(i);
        if (i <= n && num[i] == num[i + n]) {
          puts("IMPOSSIBLE");
          system("pause");
          return 0;
        }
      }
      puts("POSSIBLE");
      for (int i = 1; i <= n; i++) {
        printf("%d ", num[i] < num[i + n]);
      }
      system("pause");
      return 0;
    }
    

    无向图中的联通分量

    (egin{cases} 边双联通 o 缩点后变成一棵树\ 点双联通\ end{cases})

    边双:找边双等价于找桥

    void tir(int x ,int fa) {
      low[x] = dfn[x] ++cnt;
      for(int i = head[x] ; i ; i = e[i].net) {
        int to = e[i].to;
        if(!dfn[to]) {
          tir(to, x) 
          low[x] = min(low[x], low[to]);
          if(low[to] > dfn[x]) cut[x] = 1;
        } else 
          if(dfn[to] < dfn[x] && to != fa) 
            low[x] = min(low[x],dfn[to]);
      }
    
    }
    

    二分图

    匈牙利算法

    朋友圈

    • A:A国中最短多选择两个人
    • B:B国中可以根据奇偶性分两类

    upd 2021.4.14

    我怕是属鸽子的自己咋这么多东西都没写,也不想补就这样放出来吧,复习的时候再补补
    至于二分图,大概我是不会写的,我可能会写网络流? 因为二分图想想实现的东西网络流都能够实现


    Future never has to do with past time,but present time dose.
  • 相关阅读:
    Jmeter之Constant Timer与constant throughput timer的区别(转)
    JMeter Exception: java.net.BindException: Address already in use: connect(转)
    jmeter的jtl日志转html报告常见报错笔记
    jmeter 启动jmeter-server.bat远程调用报错: java.io.FileNotFoundException: rmi_keystore.jks (系统找不到指定的文件。)
    jmeter5.0生成html报告 快速入门
    图片转字符画 【学习ing】
    python生成个性二维码学习笔记
    Processing 3!
    Python Selenium定位元素常用解决办法
    js 获取元素坐标 和鼠标点击坐标
  • 原文地址:https://www.cnblogs.com/-wzd233/p/14658653.html
Copyright © 2020-2023  润新知