2024 蒼穹計劃好題分享 (1)

hztmax0發表於2024-08-16

QOJ 3276 出題高手

注意到資料隨機,這提示我們有用的區間數並不多,考慮將這些區間算出來,計算一個區間對一次詢問的貢獻。

Solution 1

做字首和 \(s_i = \sum\limits_{k = 1}^i a_i\),考慮一對 \((i, j)\) 的價值是 \(\frac{(s_j - s_i)^2}{j - i}\)\(0 \le i < j \le n\)),我們先考慮 \(s_j > s_i\) 的情況(\(s_j \le s_i\) 類似),此時固定一個 \(j\),考慮兩個決策點 \(i_0, i_1\),若 \(i_0 > i_1\)\(s_{i_0} < s_{i_1}\),則 \(i_0\) 一定優於 \(i_1\),那麼有用的 \(i\) 的數量是隨機序列字首和的單調棧長度,是 \(O(\sqrt n)\) 的,那麼總區間數是 \(O(n \sqrt n)\),無法接受。

Solution 2

前面的做法總區間數過多,其實是因為只考慮的左端點的限制,沒有考慮右端點的限制。考慮 \(s_j > s_i\) 的情況,我們注意一對 \((i, j)\) 有用的必要條件是 \(\forall i \le i_0 < j_0 \le j\)\(s_j - s_i > s_{j_0} - s_{i_0}\),那麼其實我們要求 \(\forall k \in [i + 1, j - 1], s_i < s_k < s_j\),考慮若存在 \(s_k \le s_i\),將 \(i\) 移到 \(k\) 一定變優,若存在 \(s_k \ge s_j\),將 \(j\) 移到 \(k\) 一定變優。\(s_j \le s_i\) 類似。

\((i, j)\) 的數量為 \(cnt\),考慮 \(s_i < s_k < s_j\) 的限制用單調棧即可維護,使得每對 \(i, j\) 只被算 \(1\) 次,時間複雜度 \(O(cnt)\)。設詢問區間為 \([l, r]\)\((i, j)\) 對其有貢獻當且僅當 \(l \le i + 1 \le j \le r\),樹狀陣列維護即可,時間複雜度 \(O(cnt \log n)\)

考慮 Solution 1 只考慮左端點的限制,將 \(O(n^2)\) 的區間數縮小到 \(O(n \sqrt n)\),相當於將問題規模指數級縮小了 \(\frac{3}{4}\)\(x \gets x^{\frac{3}{4}}\))。由於資料隨機,考慮一個端點的限制可以認為是隨機排除一些區間,所以是做兩遍 \(x \gets x^{\frac{3}{4}}\),故在 Solution 2 中,\(cnt = O(n^{\frac{9}{8}})\)。這些分析已經過打表證明。

時間複雜度 \(O((n^{\frac{9}{8}} + m) \log n)\)。對於 \(\text{Subtask2, 3}\) 需要特判,時間複雜度 \(O(n^{\frac{9}{8}})\)

Code
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <chrono>
#include <random>

using namespace std;

mt19937 R(chrono::steady_clock::now().time_since_epoch().count());

int Rand (int l, int r) {
  return uniform_int_distribution<int>(l, r)(R);
} 

using LL = long long;

struct Frac {
  LL x, y; 

  Frac () : x(0), y(1) {}
  Frac (LL _x, LL _y) : x(_x), y(_y) {
    LL g = __gcd(x, y);
    x /= g, y /= g;
  }

  bool operator< (Frac a) const { return (__int128)x * a.y < (__int128)a.x * y; }
  void Print () { cout << x << ' ' << y << '\n'; } 
}; 

struct Node {
  int o, l, r, v;
};

const int N = 2e3 + 5; 

int n, m;
int a[N], s[N], Rles[N], Rgre[N], tr[N], ans[N][N];
vector<Frac> fvec;
vector<Node> arr;

void Init (int o, int *ret) {
  stack<int> st;
  fill(ret + 1, ret + n + 1, n + 1);
  for (int i = 1; i <= n; ++i) {
    while (!st.empty() && (!o ? s[i] < s[st.top()] : s[i] > s[st.top()])) {
      ret[st.top()] = i;
      st.pop();
    }
    st.push(i);
  }
}

void Insert (int x, int y) {
  for (; x <= n; x += (x & -x)) {
    tr[x] = max(tr[x], y);
  }
}

int Query (int x) {
  int res = 0; 
  for (; x; x -= (x & -x)) {
    res = max(res, tr[x]);
  }
  return res;
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  if (n > 2000) return 0;
  for (int i = 1; i <= n; ++i) {
    cin >> a[i];
    s[i] = s[i - 1] + a[i];
  }
  Init(0, Rles), Init(1, Rgre);
  vector<pair<pair<int, int>, Frac>> vec;
  Frac mx = Frac(0, 1);
  for (int i = 1; i <= n; ++i) {
    for (int j = i, k = i; j < Rles[i] || k < Rgre[i]; ) {
      auto Add = [&](int p) -> void {
        vec.push_back({{i, p}, Frac(1ll * (s[p] - s[i - 1]) * (s[p] - s[i - 1]), p - i + 1)});
        mx = max(mx, vec.back().second);
      };
      if (j == k) {
        Add(j), j = Rgre[j], k = Rles[k];
      }
      else if ((j < Rles[i]) && (k >= Rgre[i] || j < k)) {
        Add(j), j = Rgre[j];
      }
      else {
        Add(k), k = Rles[k];
      }
    }
  }
  if (n > int(1e5)) {
    mx.Print();
    return 0; 
  }
  sort(vec.begin(), vec.end(), [&](pair<pair<int, int>, Frac> a, pair<pair<int, int>, Frac> b) -> bool {
    return a.second < b.second;
  });
  fvec.resize(vec.size()), arr.resize(vec.size());
  for (int i = 0; i < vec.size(); ++i) {
    fvec[i] = vec[i].second; 
    arr[i] = Node({0, vec[i].first.first, vec[i].first.second, i}); 
    ans[arr[i].l][arr[i].r] = arr[i].v;
  }
  for (int l = 2; l <= n; ++l) {
    for (int i = 1, j = l; j <= n; ++i, ++j) {
      ans[i][j] = max(ans[i][j], max(ans[i][j - 1], ans[i + 1][j]));
    }
  }
  cin >> m;
  for (int i = 1, l, r; i <= m; ++i) {
    cin >> l >> r;
    fvec[ans[l][r]].Print();
  }
  return 0;
}

QOJ 3047 Wind of Change

P6199 [EER1] 河童重工 弱化版。

考慮在 \(T_1\) 上點分治,假設當前層根結點為 \(r\),設 \(pre_i\) 表示結點 \(i\) 到結點 \(r\)\(T_1\) 上的距離,則對於結點 \(i\),所求即為 \(pre_i + \min\limits_{j \neq i} pre_j + dist(T_2, i, j)\)

對於當前點分治到的所有點在 \(T_2\) 上建一棵虛樹。在虛樹上考慮,相當於點有點權,邊有邊權,對於每個 \(i\) 找一個 \(j(j \neq i)\),最小化 \(\text{path}(i, j)\) 的邊權加 \(j\) 的點權。那麼這是入門樹形 \(\text{dp}\)\(f_{i, 0/1}\) 表示 \(i\) 子樹內最小/次小權值(要求一個兒子只能貢獻一個點),\(g_i\) 表示 \(i\) 子樹外最小權值,做 \(2\) 遍樹形 \(\text{dp}\) 即可。

時間複雜度 \(O(n \log^2 n)\)

Code
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
using LL = long long;
using Pii = pair<int, int>;

const int N = 2.5e5 + 5, M = 18;
const LL Inf = 1e18;

int n, fa2[N][M], dep2[N], dfn2[N], now;
LL f[N][2], g[N];
LL pre1[N], pre2[N], ans[N];
bool dvis[N], inv[N];
vector<Pii> e1[N], e2[N];
vector<pair<int, LL>> vt[N];

void Dfs2 (int u) {
  dfn2[u] = ++now;
  dep2[u] = dep2[fa2[u][0]] + 1; 
  for (int k = 1; k < M; ++k) {
    fa2[u][k] = fa2[fa2[u][k - 1]][k - 1];
  }
  for (auto i : e2[u]) {
    int v = i.first, w = i.second;
    if (v != fa2[u][0]) {
      pre2[v] = pre2[u] + w;
      fa2[v][0] = u, Dfs2(v);
    }
  }
}

int Lca2 (int u, int v) {
  if (dep2[u] < dep2[v]) swap(u, v);
  for (int k = M - 1; ~k; --k) {
    if (dep2[u] - dep2[v] >= (1 << k)) {
      u = fa2[u][k];
    }
  }
  if (u == v) return u;
  for (int k = M - 1; ~k; --k) {
    if (fa2[u][k] != fa2[v][k]) {
      u = fa2[u][k], v = fa2[v][k];
    }
  }
  return fa2[u][0];
} 

void Solve (int u) {
  int cnt = 0, rt = 0; 
  auto Find_root = [&](auto &self, int u, int r) -> int {
    int siz = 1, mx = 0; 
    for (auto i : e1[u]) {
      int v = i.first, w = i.second;
      if (!dvis[v] && v != r) {
        int r = self(self, v, u);
        mx = max(mx, r), siz += r;
      }
    }
    if (mx <= cnt / 2 && cnt / 2 <= siz - 1) {
      rt = u;
    }
    return siz;
  };
  cnt = Find_root(Find_root, u, 0);
  Find_root(Find_root, u, 0);
  vector<int> vec;
  auto Dfs = [&](auto &self, int u, int r) -> void {
    vec.push_back(u), inv[u] = 1; 
    for (auto i : e1[u]) {
      int v = i.first, w = i.second;
      if (!dvis[v] && v != r) {
        pre1[v] = pre1[u] + w;
        self(self, v, u);
      }
    }
  }; 
  pre1[rt] = 0, Dfs(Dfs, rt, 0);
  auto cmp = [&](int i, int j) -> bool {
    return dfn2[i] < dfn2[j]; 
  };
  sort(vec.begin(), vec.end(), cmp);
  for (int i = 1, s = vec.size(); i < s; ++i) {
    vec.push_back(Lca2(vec[i - 1], vec[i]));
  } 
  sort(vec.begin(), vec.end(), cmp);
  vec.resize(unique(vec.begin(), vec.end()) - vec.begin());
  for (int i = 1; i < vec.size(); ++i){ 
    int z = Lca2(vec[i - 1], vec[i]), x = vec[i];
    vt[z].push_back({x, pre2[x] - pre2[z]});
    vt[x].push_back({z, pre2[x] - pre2[z]});
  }
  auto DP1 = [&](auto &self, int u, int r) -> void {
    f[u][0] = f[u][1] = Inf;
    if (inv[u]) f[u][0] = pre1[u]; 
    for (auto i : vt[u]) {
      int v = i.first;
      LL w = i.second;
      if (v != r) {
        self(self, v, u);
        if (f[v][0] + w < f[u][0]) {
          f[u][1] = f[u][0], f[u][0] = f[v][0] + w;
        }
        else {
          f[u][1] = min(f[u][1], f[v][0] + w);
        }
      }
    }
  };
  auto DP2 = [&](auto &self, int u, int r) -> void {
    if (inv[u]) ans[u] = min(ans[u], pre1[u] + min(f[u][0] == pre1[u] ? f[u][1] : f[u][0], g[u]));
    for (auto i : vt[u]) {
      int v = i.first;
      LL w = i.second;
      if (v != r) {
        g[v] = w + min(g[u], (f[v][0] + w == f[u][0] ? f[u][1] : f[u][0]));
        self(self, v, u);
      }
    }
  };
  DP1(DP1, rt, 0), g[rt] = Inf, DP2(DP2, rt, 0);
  for (int i = 0; i < vec.size(); ++i) {
    vt[vec[i]].clear(), inv[vec[i]] = 0, pre1[vec[i]] = 0; 
  }
  dvis[rt] = 1; 
  for (auto i : e1[rt]) {
    if (!dvis[i.first]) Solve(i.first);
  }
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1, u, v, w; i < n; ++i) {
    cin >> u >> v >> w;
    e1[u].push_back({v, w});
    e1[v].push_back({u, w});
  }
  for (int i = 1, u, v, w; i < n; ++i) {
    cin >> u >> v >> w;
    e2[u].push_back({v, w});
    e2[v].push_back({u, w});
  }
  fill(ans + 1, ans + n + 1, Inf);
  Dfs2(1), Solve(1);
  for (int i = 1; i <= n; ++i) {
    cout << ans[i] << '\n';
  } 
  return 0; 
}

QOJ5071 Check Pattern is Good

\(\text{dp}\) 不好做,貪心不好做,所以考慮網路流。

這裡將 WB 換成 \(0/1\)。一個合法的 Pattern 相當於一個 \(01\) 交錯的 \(2 \times 2\) 矩形,它對答案有 \(1\) 的貢獻。

建立 \(0/1\) 變數模型,設 \(a_{i, j, k = 0/1} = 0/1\) 表示以 \((i, j)\) 為左上角的 \(2 \times 2\) 矩形,是否形成一個左上角為數字 \(k\) 的 Pattern,若 \(a_{i, j, k} = 1\) 則對答案有 \(1\) 的貢獻,由於是最小割,最大貢獻不好做,轉化成 \(a_{i, j, k} = 0\)\(1\) 的代價,我們要最小化代價。

然後考慮有哪些限制。首先 \(a_{i, j, 0}\)\(a_{i, j, 1}\) 不能同時取 \(1\),並且位置 \((i, j)\) 的值不能和 \((i, j + 1), (i + 1, j - 1), (i + 1, j), (i + 1, j + 1)\) 的值產生矛盾,最後就是不能和題目中給出的資訊衝突,如果這些限制全部不滿足,代價為 \(\infty\),顯然存在滿足所有限制的方案。

我們發現出現了對於 \(a = 0 \and b = 0\)\(a = 1 \and b = 1\) 的限制,於是將所有變數 \(a_{i, j, k}\) 異或 \(k\),再異或 \((i + j) \bmod 2\),前者是 \(01\) 建模常用技巧了,後者是由於這道題的 Pattern 本身就形如 \(01\) 交錯,這容易使我們想到黑白染色。這樣處理後就是標準的 \(01\) 變數模型了(具體限制內容從略)。

由於是流題,時間複雜度不做分析。

Code
#include <iostream>
#include <vector>
#include <queue>
#include <cassert>

using namespace std;

const int Inf = 1e9;

namespace Dinic {
  const int N = 2e4 + 5;

  struct E {
    int v, w, id;
  }; 

  int n, s, t;
  int dis[N];
  vector<E> e[N];
  vector<E>::iterator now[N];

  void Init (int _n, int _s, int _t) {
    n = _n, s = _s, t = _t;
    fill(e + 1, e + n + 1, vector<E>());
  }

  bool Bfs () {
    fill(dis, dis + n + 1, -1), dis[s] = 0; 
    queue<int> q;
    for (q.push(s); !q.empty(); q.pop()) {
      int u = q.front();
      for (auto i : e[u]) {
        int v = i.v, w = i.w;
        if (w && dis[v] == -1) {
          dis[v] = dis[u] + 1;
          q.push(v);
        }
      }
    }
    return dis[t] != -1;
  }

  int Dfs (int u, int d) {
    if (u == t) return d;
    int res = 0; 
    for (auto i = now[u]; i != e[u].end() && d; ++i) {
      now[u] = i; 
      int v = i->v, &w = i->w, id = i->id;
      if (dis[v] == dis[u] + 1 && w) {
        int r = Dfs(v, min(w, d));
        if (!r) dis[v] = -1;
        res += r;
        d -= r;
        w -= r;
        e[v][id].w += r;
      }
    } 
    return res;
  }

  int Max_flow () {
    int res = 0; 
    while (Bfs()) {
      for (int i = 1; i <= n; ++i) {  
        now[i] = e[i].begin();
      }
      res += Dfs(s, Inf);
    }
    return res;
  }

  void Add_edge (int u, int v, int w) {
    e[u].push_back((E){v, w, int(e[v].size())});
    e[v].push_back((E){u, 0, int(e[u].size()) - 1});
  }
}

namespace _01_Limitation {
  void Init (int n) {
    Dinic::Init(n + 2, n + 1, n + 2);
  }

  void Lim1 (int x, int o, int w) {
    if (!o) {
      Dinic::Add_edge(x, Dinic::t, w);
    } 
    else {
      Dinic::Add_edge(Dinic::s, x, w);
    }
  } 

  void Lim2 (int x, int a, int y, int b, int w) {
    assert(a ^ b == 1); 
    if (a && !b) swap(x, y);
    Dinic::Add_edge(x, y, w);
  }

  pair<int, vector<bool>> Solve () {
    int res = Dinic::Max_flow();
    vector<bool> vec(Dinic::n - 2); 
    for (int i = 0; i < vec.size(); ++i) {
      vec[i] = Dinic::dis[i + 1] == -1;
    }
    return {res, vec};
  }
}

const int N = 105;

int n, m;
int a[N][N]; 

int Id (int x, int y, int o) { return (x - 1) * (m - 1) + y + o * (n - 1) * (m - 1); }

int main () {
  cin.tie(0)->sync_with_stdio(0);
  int T;
  cin >> T;
  while (T--) {
    cin >> n >> m;
    _01_Limitation::Init((n - 1) * (m - 1) * 2);
    for (int i = 1; i <= n; ++i) {
      for (int j = 1; j <= m; ++j) {
        char c;
        cin >> c;
        if (c == 'W') 
          a[i][j] = 0;
        else if (c == 'B')
          a[i][j] = 1; 
        else 
          a[i][j] = -1;
      }
    }
    for (int i = 1; i < n; ++i) {
      for (int j = 1; j < m; ++j) {
        int v = (i ^ j) & 1; 
        _01_Limitation::Lim2(Id(i, j, 0), v ^ 1, Id(i, j, 1), v, Inf);
        _01_Limitation::Lim1(Id(i, j, 0), v, 1);
        _01_Limitation::Lim1(Id(i, j, 1), v ^ 1, 1);
        if (j + 1 < m) {
          _01_Limitation::Lim2(Id(i, j, 0), v ^ 1, Id(i, j + 1, 0), v, Inf);
          _01_Limitation::Lim2(Id(i, j, 1), v, Id(i, j + 1, 1), v ^ 1, Inf);
        }
        if (i + 1 < n) {
          _01_Limitation::Lim2(Id(i, j, 0), v ^ 1, Id(i + 1, j, 0), v, Inf);
          _01_Limitation::Lim2(Id(i, j, 1), v, Id(i + 1, j, 1), v ^ 1, Inf);
        }
        if (i + 1 < n && j + 1 < m) {
          _01_Limitation::Lim2(Id(i, j, 0), v ^ 1, Id(i + 1, j + 1, 1), v, Inf);
          _01_Limitation::Lim2(Id(i, j, 1), v, Id(i + 1, j + 1, 0), v ^ 1, Inf);
        }
        if (i + 1 < n && j > 1) {
          _01_Limitation::Lim2(Id(i, j, 0), v ^ 1, Id(i + 1, j - 1, 1), v, Inf);
          _01_Limitation::Lim2(Id(i, j, 1), v, Id(i + 1, j - 1, 0), v ^ 1, Inf);
        }
        if (a[i][j] == 1 || a[i][j + 1] == 0 || a[i + 1][j] == 0 || a[i + 1][j + 1] == 1)  
          _01_Limitation::Lim1(Id(i, j, 0), v ^ 1, Inf);
        if (a[i][j] == 0 || a[i][j + 1] == 1 || a[i + 1][j] == 1 || a[i + 1][j + 1] == 0) 
          _01_Limitation::Lim1(Id(i, j, 1), v, Inf);
      }
    }
    pair<int, vector<bool>> ans = _01_Limitation::Solve();
    for (int o = 0; o < 2; ++o) {
      for (int i = 1; i < n; ++i) {
        for (int j = 1; j < m; ++j) {
          if (ans.second[Id(i, j, o) - 1] ^ o ^ ((i ^ j) & 1)) {
            a[i][j] = o, a[i + 1][j] = o ^ 1, a[i][j + 1] = o ^ 1, a[i + 1][j + 1] = o;
          }
        }
      }
    }
    cout << (n - 1) * (m - 1) * 2 - ans.first << '\n';
    for (int i = 1; i <= n; ++i) {
      for (int j = 1; j <= m; ++j) {
        cout << (a[i][j] == 1 ? 'B' : 'W');
      }
      cout << '\n';
    }
  }
  return 0; 
}

QOJ 5070 Check Pattern is Bad

猜結論:類似 NOI2024 D1T3 的,若當前局面無緊限制則一定有解(本題中,我們稱一個緊限制為一個 \(2 \times 2\) 矩形,滿足正好差一個 ? 形成 Pattern)。

考慮以下命題:

  • 對於任意一個沒有緊限制的局面,我們任選一個當前為 ? 的格子,它一定滿足:存在一種對於該格子的填寫方案(填 WB),使得緊限制只會增加不超過一個。

如果這個結論成立的話,那麼在無緊限制的情況下,我們這樣做只會使緊限制增加一個,並且一個緊限制的消除最多隻會產生一個緊限制,那麼我們依次處理就好了。

但是我們發現前面的結論是不對的,考慮以下情況:

WBW
???
BWB

此時對於中間的那個 ?,無論填什麼都會使緊限制增加 \(2\) 個。

我們觀察什麼時候結論不成立,發現當且僅當,選出的格子作為中心的 \(3 \times 3\) 矩形中(若選出的格子在邊界上顯然成立),恰好有一行或一列是 ?,且剩下的兩行/兩列 WB 交錯,且這兩行/兩列恰好對偶。這個可以透過打表發現,也容易分討證明。

也就是說,所有使得上述結論不成立的 \(3 \times 3\) 矩形,都可以由前面給出的矩形旋轉得到(這也說明不成立的情況只有 \(4\) 種)。

但是我們容易發現,此時除了中心點,還有其他兩個 ? 可供選擇,所以我們只要每次選擇第一個? 就可以避免這種情況了。

時間複雜度 \(O(nm)\)

Code
#include <iostream>
#include <queue>
#include <cassert>

using namespace std;

const int N = 105;

int n, m; 
char a[N][N];
queue<pair<pair<int, int>, char>> q;

int Count (int x, int y) {
  if (!x || !y || x >= n || y >= m) return 0; 
  return ((a[x][y] == 'B') + (a[x][y + 1] == 'W') + (a[x + 1][y] == 'W') + (a[x + 1][y + 1] == 'B')) % 3 == 0 &&
         (a[x][y] == '?') + (a[x][y + 1] == '?') + (a[x + 1][y] == '?') + (a[x + 1][y + 1] == '?') == 1; 
}

void Add_limit (int x, int y) {
  char ch = ((a[x][y] == 'B') + (a[x][y + 1] == 'B') + (a[x + 1][y] == 'B') + (a[x + 1][y + 1] == 'B') == 2 ? 'B' : 'W');
  for (int ax = x; ax <= x + 1; ++ax) {
    for (int ay = y; ay <= y + 1; ++ay) {
      if (a[ax][ay] == '?') {
        q.push({{ax, ay}, ch});
      }
    }
  }
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  int T;
  cin >> T;
  while (T--) {
    [&]() -> void {
      cin >> n >> m; 
      for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
          cin >> a[i][j];
        }
      }
      for (int i = 1; i < n; ++i) {
        for (int j = 1; j < m; ++j) {
          if (Count(i, j)) {
            Add_limit(i, j);
          }
        }
      }
      for (int t = 1; t <= n * m; ) {
        if (q.empty()) {
          while (t <= n * m) {
            int x = (t - 1) / m + 1, y = (t - 1) % m + 1;
            if (a[x][y] == '?') {
              char ret = '?';
              for (int o = 0; o < 2; ++o) {
                char ch = !o ? 'W' : 'B';
                a[x][y] = ch;
                if (Count(x - 1, y - 1) + Count(x - 1, y) + Count(x, y - 1) + Count(x, y) <= 1) {
                  ret = ch;
                }
              }
              assert(ret != '?');
              q.push({{x, y}, ret});
              ++t;
              break;
            }
            ++t;
          }
          if (q.empty()) break;
        }
        pair<pair<int, int>, char> tp = q.front();
        q.pop();
        int x = tp.first.first, y = tp.first.second;
        char c = tp.second;
        a[x][y] = c;
        if (Count(x - 1, y - 1)) Add_limit(x - 1, y - 1);
        if (Count(x - 1, y)) Add_limit(x - 1, y);
        if (Count(x, y - 1)) Add_limit(x, y - 1);
        if (Count(x, y)) Add_limit(x, y);
      }
      for (int i = 1; i < n; ++i) {
        for (int j = 1; j < m; ++j) {
          if (a[i][j] == 'B' && a[i][j + 1] == 'W' && a[i + 1][j] == 'W' && a[i + 1][j + 1] == 'B' || 
              a[i][j] == 'W' && a[i][j + 1] == 'B' && a[i + 1][j] == 'B' && a[i + 1][j + 1] == 'W') {
            cout << "NO" << '\n';
            return;
          } 
        }
      }
      cout << "YES" << '\n';
      for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
          cout << a[i][j];
        }
        cout << '\n';
      }
    }();
  }
  return 0; 
}

QOJ 6406 Stage Clear

看到 \(n + m \le 72\) 這樣奇怪的限制,並且容易發現這題很難 Meet in the middle,所以考慮資料分治。

Solution1

考慮倒著做狀壓 \(\text{dp}\),設 \(f_S\) 表示當前局面下還剩點集 \(S\) 有怪沒打,此時需要 \(HP\) 為多少才能打贏這些怪,轉移列舉前一個打死的怪 \(x\)\(dp_{S \cup x} \gets \max(f_S - b_x, 0) + a_x\)

時間複雜度 \(O(2^n \times n)\)

Solution2

考慮另一種思路,注意到對於每一條邊 \(u \rightarrow v\),一定有 \(u < v\),這說明結點順序是原圖的一個拓撲序。所以最終方案一定可以表示為:從小往大加點,每次向一個已經存在的點連邊,構成一棵樹,限制就是要打一個點上的怪物,必須先打它的所有祖先。

對這棵樹的形態進行搜尋,一個入度為 \(d\) 的點有 \(d - 1\) 種連邊方案,由於題目相當於保證存在以 \(1\) 為根的 \(\text{dfs}\) 生成樹,去掉這些樹邊,考慮一條非樹邊只會讓一個點入度 \(+1\),且非樹邊只有 \(m - n + 1\) 條,所以樹的形態最多隻有 \(2^{m - n + 1}\) 種。

考慮圖是一棵樹怎麼做,這是一個經典的樹上貪心問題。

  • 首先將 \(b_u \ge a_u\) 的點稱為優點,\(b_u < a_u\) 的點稱為劣點。

  • 對於沒有樹上的祖先限制的情況(也有是給你一個集合,你可以以任意順序選),肯定是先選優點,再選劣點。優點裡面一定先選 \(a_u\) 更小的點,劣點裡面一定先選 \(b_u\) 更大的點。

  • 現在擴充到樹上,我們將上面問題中第一個選的點稱為最優點 \(u\),我們發現樹上問題一定存在最優策略是,選完 \(u\) 的父親後馬上選 \(u\)

  • 每次找出當前最優點 \(u\)\(u\) 不為根),將 \(u\)\(u\) 的父親 \(r\) 合併。新點的資訊:\(a = \max(a_r, a_r - b_r + a_u), b = \max(b_r - a_u, 0) + b_u\)。把新點也加入最優點的考慮範圍。此時樹的點數減少了 \(1\),我們將問題規模從 \(n\) 縮小到了 \(n - 1\)

  • 重複以上過程 \(n - 1\) 次,直到樹上只有一個結點。

用堆維護最優點的選擇,時間複雜度 \(O(2^{m - n} n \log n)\),我比較懶,寫的暴力維護,時間複雜度 \(O(2^{m - n} n ^2)\)

\(n \le 25\) 時跑 Solution1,在 \(n > 25\) 時跑 Solution2 即可。

Code
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>

using namespace std;
using LL = long long;

const int N = 38;
const LL Inf = 1e18;

int n, m;
LL a[N], b[N];

namespace SolveA {
  const int S = (1 << 25);

  LL f[S];
  int re[N];

  int main () {
    for (int u, v; m--; ) {
      cin >> u >> v;
      re[v] |= (1 << (u - 1));
    }
    fill(f + 1, f + (1 << n), Inf); 
    for (int s = 0; s < (1 << n); ++s) {
      for (int x = 2; x <= n; ++x) {
        if ((s >> (x - 1)) & 1) continue;
        if ((s & re[x]) != re[x]) {
          f[s | (1 << (x - 1))] = min(f[s | (1 << (x - 1))], max(f[s] - b[x], 0ll) + a[x]);
        }
      } 
    }
    cout << f[(1 << n) - 2] << '\n';
    return 0; 
  }
}

namespace SolveB {
  vector<int> re[N];
  int fa[N], rt[N];
  LL a[N], b[N];
  LL ans = Inf;

  int Find (int x) { 
    if (rt[x] == x) return x;
    return rt[x] = Find(rt[x]);
  }

  void Calc () {
    copy(::a + 1, ::a + n + 1, SolveB::a + 1);
    copy(::b + 1, ::b + n + 1, SolveB::b + 1); 
    auto cmp = [&](int i, int j) -> bool {
      if (b[i] >= a[i]) {
        if (b[j] >= a[j]) {
          return a[i] < a[j];
        }
        return 1; 
      }
      else if (b[j] >= a[j]) {
        return 0; 
      }
      return b[i] > b[j];
    };
    iota(rt + 1, rt + n + 1, 1);
    for (int k = 1; k < n; ++k) {
      int i = 0; 
      for (int j = 2; j <= n; ++j) {
        if (rt[j] == j && (!i || cmp(j, i))) {
          i = j; 
        }
      }
      int r = Find(fa[i]);
      a[r] = max(a[r], a[r] - b[r] + a[i]), b[r] = max(0ll, b[r] - a[i]) + b[i];
      rt[i] = Find(r);
    }
    ans = min(ans, a[1]);
  }

  void Dfs (int u) {
    if (u == n + 1) {
      Calc();
      return; 
    }
    for (auto v : re[u]) {
      fa[u] = v; 
      Dfs(u + 1);
    }
  }

  int main () {
    for (int u, v; m--; ) {
      cin >> u >> v;
      re[v].push_back(u);
    }
    Dfs(2);
    cout << ans << '\n';
    return 0; 
  }
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 2; i <= n; ++i) {
    cin >> a[i] >> b[i];
  }
  if (n <= 25) return SolveA::main();
  return SolveB::main();
}

相關文章