07.30

purplevine發表於2024-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 就是這個做法的。需要類似於當前弧最佳化的東西,來繼續訪問一個點的出邊,或者說保證一條邊不會被來回走多次。除了每條邊被正反搜過去的兩遍外,樹邊可以被來回再走一次。