Loading

07.30

晚上喝多冰冰的百香果水了,肚子一直疼。/ll

可算把题单公开了。

https://vjudge.net/article/5412

CF1082G

左点右边跑最小割,割左表示点被选,割右表示边没被选。

int main() {
  int n, m; scanf("%d %d", &n, &m);
  std::vector<int> a(n);
  int s = n + m, t = n + m + 1;
  MF S(n + m + 2, s, t);
  LL ans = 0;
  for (int i = 0, x; i < n; i++) {
    scanf("%d", &x);
    S.link(s, i, x);
  }
  for (int i = n, u, v, w; i < n+m; i++) {
    scanf("%d %d %d", &u, &v, &w), --u, --v, ans += w;
    S.link(i, t, w), S.link(u, i, inf), S.link(v, i, inf);
  }
  printf("%lld\n", ans - S.mf());
}

CF1383F

最大流=最小割。因为 \(k\) 非常小,预先枚举割的是哪些边,求出该状态下的最小割,

所有情况下的合法的最小割即为最大流。

厉害的转化是,最小割让每条边只有割与不割的区别,因此才能进行 \(2^k\) 的处理。

agc031E

\(a_i\) 左侧选的点的个数不超过 \(b_i\),于是第 \(b_{i+1}\) 个选中点在 \(a_i\) 右侧。这样就把难以计数的区间问题,转化成了只与单点有关的问题。

枚举总点数,现在每个位置需要满足的坐标均已被确定,这是一个多重匹配问题。

三列点,左列每个位置的横坐标区间,右列每个位置的纵坐标区间,中间是每个点。

agc029F

不会 Hall 定理啊。

有解需要任取一个 \(\{E\}\) 的子集 \(S\) 都满足 \(|\{u | u \in E_i \in S\}| \geq |S| + 1\),否则会连出环。

根据 Hall 定理这其实就是充要条件。

钦定 \(1\) 当根,从每个点出发给它匹配一个父亲。具体而言,左点右集合跑最大匹配。当确定一个点的父亲时,访问所有它在的集合,将集合匹配的那个点挂到它下面,递归即可。

int main() {
  int n; scanf("%d", &n);
  int s = 2 * n, t = s + 1; 
  std::vector<std::vector<int>> e(n);
  MF S(t + 1, s, t);
  for (int i = n, x; i < n + n - 1; i++) {
    scanf("%d", &x);
    S.link(i, t, 1);
    for (int j = 0, k; j < x; j++) {
      scanf("%d", &k), --k;
      e[k].push_back(i - n);
      S.link(k, i, 1);
    }
  }
  for (int i = 1; i < n; i++)
    S.link(s, i, 1);
  if (S.mf() < n - 1) return printf("-1\n"), 0;
  // printf("-----\n");
  std::vector<std::pair<int, int>> ans(n - 1);
  std::vector<int> f(n - 1);
  for (int u = 1; u < n; u++) {
    for (const auto &[v, w, id] : S.e[u]) if (v != s && !w)
      f[v - n] = u; //, printf("%d %d %d\n", u, v, w);
  }
  // for (int i = 0; i < n - 1; i++)
  //   printf("%d : %d\n", i, f[i]);
  // printf("\n");
  std::vector<int> vis(n - 1);
  std::queue<int> q; q.push(0);
  while (q.size()) {
    int u = q.front(); q.pop();
    for (auto v : e[u]) {
      if (!vis[v]) vis[v] = 1, ans[v] = {u, f[v]}, q.push(f[v]);
    }
  };
  for (const auto &[u, v] : ans) printf("%d %d\n", u + 1, v + 1);
}

gym102201J

QOJ6308

考虑差分,每次操作事实上是把 \(b_{l_i}, b_{r_i}\) 变成 \(1\),再把中间变成 \(0\)

于是若两个区间交叉,则中间两个端点不能均为 \(1\)

可以 bitset 优化建图或主席树跑二分图最大独立集,后者还没搞懂。

P3227

切糕。

CF1630F

不能出现长度 \(\geq 3\) 的链。于是一个点只有出度或只有入度。

拆点,\(u\)\(u'\) 分别表示只有出度和只有入度。我们希望这是一张具有偏序关系的图,为了跑最大独立集即最小点覆盖。

\(u\)\(u'\) 不能同时出现

CF786E

如果不考虑点数边数的话建图是显然的,于是树剖套线段树优化最小割建图。

gym103855I

合并珠子就是把两个点合并,限制流量就建新点中间连边限制流量,丢珠子就把边连回一开始的点。最后剩的珠子全部扔掉。最后跑一个无源汇可行流即可,每个点有流表示是红色,否则表示是蓝色。

合并 \(i, j\):建新点 \(k\)\(i \to k, j \to k\)

扔掉 \(i\):假设 \(i\) 现在在的点为 \(j\),连边 \(j \to i\),其中 \(i\) 是珠子一开始的地方。

P8501

好厉害,不是特别懂。

通过神秘的分析发现是答案在两个变量的凸包上,然后用最小乘积生成树的方法把凸包给用 wqs 二分类似物求出来。至于求的过程用切糕模型?

agc059C

猜如果还没出现 \((a, c)\),则 \((a, b), (b, c)\) 的答案不能一样,事实也如此。为什么?

假如有更长的链 \(a \to b \to c \to d\),则 \((a, c), (b, d)\) 必须在整条链前面被问,但此时会确定 \(c \to d\)\(a \to c\),于是 \((a, d)\) 会不用问。

于是并查集维护 2-sat,最后剩下的集合间的大小关系随意钦定,只要两两关系合法就有唯一解。

CF587D

若一个点连了三条同色边,则无解。

因此把某种颜色的边拎出来,不是环就是链。

二分答案,奇环无解,偶环和链只有两种解,跑个 2-sat。

晚点继续想。

CF1137C

拆点,如果不存在每个博物馆只能进一次的限制就直接 dp。否则缩点,每个博物馆就只会被走一次。

可以证明一个博物馆的两天若连通,则一定在同连通块内,不会被统计多。证明考虑这相当于在 \(\bmod d\) 意义下做加法,若 \(i\) 可达 \(i+j\),则把 \(+j\) 操作做 \((d-1)\) 次等于 \(-j\),于是同一个博物馆的两天必定属于同一强连通分量,或不连通。

跑一个 Tarjan 后 dp 即可。

没有来源的题

\(n\) 个点的图,初始没有边,然后加入 \(m\) 条边,保证无重边自环。每次加入后求把图拓展为竞赛图后强连通分量的最小值。

\(n \leq 2 \cdot 10^5, m \leq 2 \cdot 10^6\)

点数 \(\geq 3\) 的补图连通块一定可以通过补充位于同一强连通分量中。

求出加完 \(m\) 条边后补图的所有连通块,这些连通块只会合并不会分裂,并且不会超过 \(O(\sqrt{m})\) 个,因此合并时可以暴力合并。

P5811

不妨让 \(a < b < c\),显然只要找到 \(a, b\) 就行了,因为如果找到了 \(a, c\) 可以把 \(c\) 中的一些点剥掉。

先考虑树的情况。\(a < n/3, n/3 < b < n/2\),如果一个连通块大小大于 \(a\),则 \(a\) 在其中产生,\(b\) 在剩余的树中产生。否则认为无解。

否则随便找一个点跑 dfs 树,取 dfs 树的重心,如果有重心的子树大于 \(a\) 则做完了;否则维护一个集合 \(S\),如果子树可以通过返祖边到达子树上面则不加入 \(S\),否则加入 \(S\)。现在重心的子树变少了,按大小从小到大排列并试图加入 \(S\),当 \(|S|\) 第一次 \(\geq a\) 时认为此时把 \(S\) 作为 \(a\) 合法。

证明晚点补。

gym102759C

双极定向.jpg

耳分解再放送。

\(f_{S, u, v}\):已经加入的集合是 \(S\),现在位于点 \(u\),要走到点 \(v\)

可以生成边双图。

BEST 定理

喵喵喵?

qoj8056

有点不会诶,结束时找老师再问了问还是不是特别懂。

\(2m\) 次是用于走边的,\(2n\) 次是用于 dfs 的。具体而言,考虑第一次进入每个点时的连边,显然它们构成一棵树。当枚举一个点的出边时,如果这点被访问过就走回来,否则继续去访问它。当走走走到走完所有边时,一定在初始点,因为此时每个点进的次数等于出的次数。此时按之前确定的树做 dfs 到找到新点继续访问。

第一次没懂,其实 std 就是这个做法的。需要类似于当前弧优化的东西,来继续访问一个点的出边,或者说保证一条边不会被来回走多次。除了每条边被正反搜过去的两遍外,树边可以被来回再走一次。

posted @ 2024-07-30 21:40  purplevine  阅读(15)  评论(0编辑  收藏  举报