CF786E ALT 题解

为什么你们第一眼都能想到最小割,我第一眼都只能想到费用流。

为什么你们的做法都这么短,我一写就是 \(5KB\)

费用流有一个基本矛盾,就是守卫只需拥有一只狗和每一个人都需要守卫有狗的基本矛盾。由于需求与供给不平衡,所以流量不好确定。如果有人费用流过了来长沙火车站,疯狂星期四我V你50。

由于最小,我们想到最小割。我们想要两种割边,一种割树上的边,一种割人。

因此我们这么构造:对于树上每条边,我们建立一个入点一个出点,源点朝入点连边,流量为 \(1\),出点向汇点连边,流量为 \(inf\)(只需要割入点就行)。

对于每个人走过的路径 \([l,r]\) (经过树链剖分处理后变为一个区间),我们将区间里的点各自分成两个点之后再分别连到一个新点上,再把两个新点连起来,流量为 \(1\),代表一个人。

你发现了区间连边的操作,于是可以线段树优化建图。

提供样例一的完整建图,其中 \(1\) 为源点,\(2\) 为汇点:

略微卡常。

#include <bits/stdc++.h>
#define F(i, n) for (int i = 1; i <= n; ++i)
#define ll long long
using namespace std;
const ll N = 1e6 + 5, INF = 5e7;

template<typename T> bool chkmax(T &a, T b) { return a < b ? (a = b, 1) : 0; }
template<typename T> bool chkmin(T &a, T b) { return a > b ? (a = b, 1) : 0; }
template<typename T> T read() { T a;cin >> a;return a; }

inline int read() {
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}


int n, m, s, t;

template<const int M>
struct graph {
  int st[M], nx[M * 6], to[M * 6], cur[M], cnt = 1;
  int id[M * 6];
  int val[M * 6];
  void add(int u, int v, int w, int i) {
    to[++cnt] = v;val[cnt] = w;nx[cnt] = st[u];st[u] = cnt;
    id[cnt] = i;
  }
};
#define go(g, u, v, w) for (int i = g.cur[u], v = g.to[i], w = g.val[i]; i; i = g.nx[i], v = g.to[i], w = g.val[i])

int cnt;
graph<N> g;
namespace Graph {
  void add(int u, int v, int w, int id) {
    g.add(u, v, w, id);
    g.add(v, u, 0, 0);
  }
  int lev[N];
  bool bfs(int s, int t) {
    for (int i = 1; i <= cnt; ++i) lev[i] = -1;
    lev[s] = 0;
    for (int i = 1; i <= cnt; ++i) g.cur[i] = g.st[i];
    queue<int> q;q.push(s);
    while (!q.empty()) {
      int u = q.front();q.pop();
      go(g, u, v, w) {
	if (w > 0 && lev[v] == -1) 
	  lev[v] = lev[u] + 1, q.push(v);
      }
    }
    return lev[t] != -1;
  }
  int dfs(int u, int flow, int t) {
    if (u == t) return flow;
    int res = flow;
    for (int i = g.cur[u]; i && res; i = g.nx[i]){
      int v = g.to[i], w = g.val[i];
      g.cur[u] = i;
      if (w > 0 && lev[v] == lev[u] + 1) {
	int c = dfs(v, min(w, res), t);res -= c;
	g.val[i] -= c;g.val[i ^ 1] += c;
      }
    }
    return flow - res;
  }
  int dinic(int s, int t) {
    int ans = 0;
    while (bfs(s, t)) {
      int p = dfs(s, INF, t);
      ans += p;
    }
    return ans;
  }
}
using Graph::add;
using Graph::dinic;

vector<tuple<int, int>> tree[N];
long long dfn[N];
int siz[N], son[N], top[N], dep[N], fa[N], pos[N], I[N];
namespace pou {
  int tot = 0;
  void findSon(int u, int F) {
    siz[u] = 1;fa[u] = F;
    int v, id;
    for (auto p : tree[u]) {
      tie(v, id) = p;
      if (v == F) continue;
      I[v] = id;
      dep[v] = dep[u] + 1;
      findSon(v, u);
      siz[u] += siz[v];
      if (siz[v] > siz[son[u]]) son[u] = v;
    }
    return ;
  }
  void mark(int u, int fa, int to) {
    int v, id;
    if (u != 1) {
      dfn[u] = ++tot;
      pos[tot] = u;
    }
    top[u] = to;
    if (son[u]) mark(son[u], u, to);
    for (auto p : tree[u]) {
      tie(v, id) = p;
      if (v == fa || v == son[u]) continue;
      mark(v, u, v);
    }
  }
}

int upId[N], dwId[N], upSon[N][2], dwSon[N][2], upRt, dwRt;
tuple<int, int> build(int l, int r) {
  if (l == r) {
    upId[l] = ++cnt;
    dwId[l] = ++cnt;
    return tie(upId[l], dwId[l]);
  }
  int mid = (l + r) >> 1;
  int u1 = ++cnt;
  int u2 = ++cnt;
  tie(upSon[u1][0], dwSon[u2][0]) = build(l, mid);
  tie(upSon[u1][1], dwSon[u2][1]) = build(mid + 1, r);
  
  add(upSon[u1][0], u1, INF, 0);
  add(upSon[u1][1], u1, INF, 0);
  add(u2, dwSon[u2][0], INF, 0);
  add(u2, dwSon[u2][1], INF, 0);
  
  return tie(u1, u2);
}
int v1, v2;
void lian(tuple<int, int> u, int l, int r, int fl, int fr) {
  int u1, u2; tie(u1, u2) = u;
  if (fl <= l && r <= fr) {
    add(u1, v1, INF, 0);
    add(v2, u2, INF, 0);
    return ;
  }
  int mid = (l + r) >> 1;
  if (mid >= fl) lian(tie(upSon[u1][0], dwSon[u2][0]), l, mid, fl, fr);
  if (mid < fr) lian(tie(upSon[u1][1], dwSon[u2][1]), mid + 1, r, fl, fr);
}
void jump(int u1, int u2){
  while (top[u1] != top[u2]) {
    if (dep[top[u1]] < dep[top[u2]]) swap(u1, u2);
    if (top[u1] == 1) {
      lian(tie(upRt, dwRt), 1, n - 1, dfn[son[top[u1]]], dfn[u1]);
    }
    else {
      lian(tie(upRt, dwRt), 1, n - 1, dfn[top[u1]], dfn[u1]);
    }
    u1 = fa[top[u1]];
  }
  if (u1 == u2) return ;
  if (dep[u1] > dep[u2]) swap(u1, u2);
  if (dfn[son[u1]] <= dfn[u2]) lian(tie(upRt, dwRt), 1, n - 1, dfn[son[u1]], dfn[u2]);
}

bool vis[N];
void dfs(int u) {
  vis[u] = 1;
  go(g, u, v, w) {
    if (w == 0 || vis[v]) continue;
    dfs(v);
  }
}

int main(){
  freopen("text.in", "r", stdin);

  int u, v;
  dfn[0] = INF;
  s = ++cnt;t = ++cnt;
  n = read(), m = read();

  int pd = 1;
  F(i, n - 1) {
    u = read(), v = read();
    tree[u].push_back(tie(v, i));
    tree[v].push_back(tie(u, i));
  }
  pou::findSon(1, 0);
  pou::mark(1, 0, 1);

  tie(upRt, dwRt) = build(1, n - 1);

  F(i, n - 1) {
    add(s, upId[i], 1, I[pos[i]]);
    add(dwId[i], t, INF, 0);
  }

  F(i, m) {
    u = read(), v = read();
    v1 = ++cnt; v2 = ++cnt;
    add(v1, v2, 1, i + n);
    jump(u, v);
  }
  
  printf("%d\n", dinic(s, t));
  dfs(s);
  vector<int> edge, peo;
  for (int i = g.st[s]; i; i = g.nx[i]) {
    if (vis[g.to[i]] == false && g.val[i] == 0) edge.push_back(g.id[i]);
  }
  for (int i = 2; i <= cnt; ++i) {
    for (int j = g.st[i]; j; j = g.nx[j]) {
      if (vis[i] != vis[g.to[j]] && g.id[j] > n && g.val[j] == 0) {
	peo.push_back(g.id[j] - n);
      }
    }
  }
  printf("%ld ", peo.size());
  for (auto i : peo) printf("%d ", i);
  puts("");
  printf("%ld ", edge.size());
  for (auto i : edge) printf("%d ", i);
  
  return 0;
}

posted @ 2023-07-19 15:50  _maze  阅读(16)  评论(1编辑  收藏  举报