图论 从入门到____________

图论

特殊图:

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

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

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

  • \(仙人掌\begin{cases} 边仙人掌 \to 每条边最多在一个环上\\ 点仙人掌\to 每个点最多在一个环上 \\ 点仙人掌 一定是边仙人掌 \\ \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\n", temp);
  for (int i = 1; i <= m; i++) {
    if (vis_[i]) {
      // cout << i<< ' ';
      printf("%lld %lld\n", a[i].id, a[i].dis);
    }
  }
  // system("pause");
  return 0;
}

强连通分量

2- SAT

和平委员会

  • 两个点 \(x_i \& x_j = false\)
  • \(\rightarrow 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\n", i);
  }
  system("pause");
  return 0;
}

模板

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

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

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

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

举个小样例:

要求满足的是\(a = 0 , b = 0\),那么我们就对其中一个取反\(\lnot a \to b\)\(\lnot b \to 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;
}

无向图中的联通分量

\(\begin{cases} 边双联通 \to 缩点后变成一棵树\\ 点双联通\\ \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

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


posted @ 2021-04-14 16:37  Imy_bisLy  阅读(149)  评论(0编辑  收藏  举报