JOI 2018 Final

hztmax0發表於2024-10-02

A - ストーブ (Stove)

\(n\) 個客人將要來訪,第 \(i\) 個客人的來訪時間為 \([a_i, a_i + 1]\),保證 \(\forall i \in [1, n), a_i < a_{i + 1}\)
在每個客人來訪時,你都需要保證暖爐是亮著的(初始時暖爐為熄滅狀態)。你可以在任意時刻熄滅暖爐,但每次點亮都需要消耗一根火柴,且你只有 \(k\) 根火柴,求最小的暖爐點亮時間。
\(n \le 10^5, a_i \le 10^9\)

首先使用一根火柴點亮初始時刻的暖爐,接下來你有 \(k - 1\) 次熄滅暖爐的機會。

考慮在第 \(i\) 個人到第 \(i + 1\) 個人的時間間隙中熄滅暖爐的收益為 \(a_{i + 1} - a_i - 1\),將這個收益排序並取前 \(k\) 大即可。時間複雜度 \(O(n \log n)\)

Code
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5; 

int n, k;
int a[N], b[N];

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> k; 
  for (int i = 1; i <= n; ++i) {
    cin >> a[i]; 
  }
  for (int i = 1; i < n; ++i) {
    b[i] = a[i + 1] - a[i] - 1;
  }
  int ans = a[n] - a[1] + 1;
  sort(b + 1, b + n, greater<int>());
  for (int i = 1; i < k; ++i) {
    ans -= b[i];
  }
  cout << ans << '\n';
  return 0; 
}

B - 美術展 (Art Exhibition)

\(n\) 件美術品,每件美術品有尺寸 \(A_i\) 與價值 \(B_i\),現在要選擇若干美術品作為展覽,設選擇的美術品集合為 \(S\),最大化 \(\sum\limits_{i \in S} B_i - (\max\limits_{i \in S} A_i - \min\limits_{i \in S} A_i)\)
\(1 \le n \le 5 \times 10^5, 1 \le A_i \le 10^{15}, 1 \le B_i \le 10^9\)

首先將所有美術品按照 \(A_i\) 排序,由於所有美術品價值為正,多選一定更優,所以選擇的美術品是一段區間。

\(B\) 做字首和。設選擇的區間為 \([l, r]\),則價值為 \(B_r - B_{l - 1} - A_r + A_l\)。考慮設一個左端點 \(l\) 的權值為 \(A_l - B_{l - 1}\),設一個右端點 \(r\) 的權值為 \(B_r - A_r\),線性掃一遍即可。時間複雜度 \(O(n \log n)\)

Code
#include <iostream>
#include <algorithm>

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

const int N = 5e5 + 5; 

int n;
Pii a[N];

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n;
  for (int i = 1; i <= n; ++i) {
    cin >> a[i].first >> a[i].second;
  }
  sort(a + 1, a + n + 1);
  for (int i = 1; i <= n; ++i) {
    a[i].second += a[i - 1].second;
  }
  LL mxl = 0, ans = 0; 
  for (int i = 1; i <= n; ++i) {
    mxl = max(mxl, a[i].first - a[i - 1].second);
    ans = max(ans, mxl + a[i].second - a[i].first);
  }
  cout << ans << '\n';
  return 0; 
}

C - 団子職人 (Dango Maker)

有一個 \(n \times m\) 的網格,每個糰子被放在一個格子裡,每個糰子的顏色是 R, G, 'W' 中的一種。
現在你可以從上到下,或從左到右選一個 \(1 \times 3\) 的矩形,並將其中的糰子按順序做成一個糰子串,要求三個糰子的顏色必須依次是 R, G, W
求最大的可以製作的糰子數。
\(1 \le n, m \le 3 \times 10^3\)

對於所有糰子,考慮在 G 處計算貢獻,那麼兩個糰子如果衝突,則它們一定在同一條從左上到右下的對角線上。

所以我們對於每個對角線獨立地 \(\text{dp}\)。考慮當前某個對角線的一個格子,設 \(f_0\) 為該糰子不做糰子串的方案數,\(f_1\) 表示該糰子橫著串的方案數,\(f_2\) 表示該糰子豎著串的方案數。則 \(f_1\)\(f_2\) 之間不能相互轉移。

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

Code
#include <iostream>

using namespace std;

const int N = 3e3 + 5; 

int n, m, f[3], g[3];
char s[N][N];

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      cin >> s[i][j];
    }
  }
  int ans = 0;
  for (int sum = 2; sum <= n + m; ++sum) {
    f[0] = 0, f[1] = f[2] = -N;
    for (int i = max(1, sum - m), j = sum - i; i <= n && j; ++i, --j) {
      copy(f, f + 3, g); 
      fill(f, f + 3, -N);
      f[0] = max(max(g[0], g[1]), g[2]);
      if (s[i][j] == 'G' && s[i - 1][j] == 'R' && s[i + 1][j] == 'W') {
        f[1] = max(g[0], g[1]) + 1;
      }
      if (s[i][j] == 'G' && s[i][j - 1] == 'R' && s[i][j + 1] == 'W') {
        f[2] = max(g[0], g[2]) + 1; 
      }
    }
    ans += max(max(f[0], f[1]), f[2]);
  }
  cout << ans << '\n';
  return 0; 
}

D - 定期券 (Commuter Pass)

給定一張 \(n\) 個結點 \(m\) 條邊的無向圖,邊有邊權。
現在你可以選擇一條 \(s\)\(t\) 的最短路,並將這條路徑上的邊的權值全部設為 \(1\),求操作後 \(u\)\(v\) 最短路的最小值。
\(1 \le n \le 10^5, 1 \le m \le 2 \times 10^5\)

首先考慮如何刻畫“\(s\)\(t\) 的最短路”,我們從 \(s\) 開始跑一遍 \(\text{Dijkstra}\),求出 \(d_i\) 表示 \(s\)\(i\) 的最短路,對於原圖上的無向邊拆成兩條有向邊,如果一條有向邊 \((u, v)\) 不滿足 \(d_v = d_u + w(u, v)\),則將其從圖中刪去。那麼我們將得到一張 \(\text{DAG}\),此時在新圖上每一條 \(s\)\(t\) 的路徑都是最短路。

然後我們發現答案要麼不經過修改權值的邊,此時就是 \(dis(u, v)\),要麼形如 \(u \rightarrow a \rightarrow b \rightarrow v\),其中 \(a\)\(b\)\(s\)\(t\) 的一條最短路上。則此時答案為 \(dis(u, a) + dis(v, b)\)\(dis(u, b) + dis(v, a)\)

考慮在 \(\text{DAG}\)\(\text{dp}\),設 \(r_i\) 表示 \(i\) 是否能到 \(t\)\(f_{i, 0}\) 表示 \(i\) 能到達的,且滿足 \(r_x = 1\) 的點中最小的 \(dis(v, x)\)\(f_{i, 1}\) 類似,直接 \(\text{dp}\) 即可。對於所有 \(i\)\(dis(s, i), dis(t, i)\) 可以 \(\text{Dijkstra}\) 預處理。

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

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

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

const int N = 1e5 + 5; 
const LL Inf = 1e18;

int n, m, gs, gt, s, t, deg[N];
vector<Pii> e[N];
vector<int> g[N];
LL dis[3][N], f[N][2];
bool r[N];

void Dijkstra (int st, LL *dis) {
  priority_queue<Pii, vector<Pii>, greater<Pii>> q;
  q.push({0, st});
  fill(dis + 1, dis + n + 1, Inf);
  while (!q.empty()) {
    Pii tp = q.top();
    q.pop();
    LL d = tp.first;
    int u = tp.second;
    if (d < dis[u]) {
      dis[u] = d;
      for (auto i : e[u]) {
        int v = i.first, w = i.second;
        q.push({d + w, v});
      }
    }
  }
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> m >> gs >> gt >> s >> t;
  for (int i = 1, u, v, w; i <= m; ++i) { 
    cin >> u >> v >> w;
    e[u].push_back({v, w});
    e[v].push_back({u, w});
  }
  Dijkstra(s, dis[0]), Dijkstra(t, dis[1]), Dijkstra(gs, dis[2]);
  for (int i = 1; i <= n; ++i) {
    for (auto j : e[i]) {
      if (dis[2][j.first] == dis[2][i] + j.second) {
        g[j.first].push_back(i), ++deg[i];
      }
    }
  }
  queue<int> q;
  for (int i = 1; i <= n; ++i) {
    if (!deg[i]) {
      q.push(i);
    }
  }
  fill(f[1], f[n] + 2, Inf);
  LL ans = dis[0][t];
  for (; !q.empty(); q.pop()) {
    int u = q.front();
    if (u == gt) {
      r[u] = 1; 
    }
    if (r[u]) {
      f[u][0] = min(f[u][0], dis[1][u]);
      f[u][1] = min(f[u][1], dis[0][u]);
      ans = min(ans, min(dis[0][u] + f[u][0], dis[1][u] + f[u][1]));
    }
    for (auto v : g[u]) {
      if (r[u]) {
        f[v][0] = min(f[v][0], f[u][0]);
        f[v][1] = min(f[v][1], f[u][1]);
        r[v] = 1;
      }
      if (!--deg[v]) {
        q.push(v);
      }
    }
  }
  cout << ans << '\n';
  return 0; 
}

E - 毒蛇の脫走 (Snake Escaping)

給定 \(n\),對於一個長度為 \(n\) 的二進位制數 \(x\),定義其權值為 \(a_x\)。接下來進行 \(q\) 次詢問,每次詢問格式如下:

  • 給定一個長度為 \(n\) 字串 \(s\)\(s\) 中的每個字元是 \(\texttt{0, 1, ?}\) 三者之一,對於所有將 ? 填寫成 01 的方案組成的數字,求它們的權值和,此處允許前導 \(\texttt{0}\)

\(1 \le n \le 20, 1 \le q \le 10^6\)

\(s_0, s_1, s_2\) 分別為 \(\texttt{0, 1, ?}\)\(s\) 中的出現位置,\(cnt_0 = |s_0|, cnt_1 = |s_1|, cnt_2 = |s_2|\) 分別為這三種字元在原串中的出現次數。

考慮類似根號分治,因為 \(cnt_0 + cnt_1 + cnt_2 \le 20\),所以 \(\min(cnt_0, cnt_1, cnt_2) \le 6\)。我們 \(\forall i \in [0, 2]\),對於 \(cnt_i \le 6\) 的情況分別處理。


對於 \(cnt_2 \le 6\)

由於 \(\texttt{?}\) 不超過 \(6\) 個,所以我們列舉每個 \(\texttt{?}\) 填什麼,對於所有可能的方案求和即可。

時間複雜度 \(O(2^{cnt_2})\)

對於 \(cnt_0 \le 6\)

考慮只有 \(\texttt{1}\)\(\texttt{?}\) 時怎麼做,我們將 \(\text{?}\) 看成 \(\texttt{0}\),那麼所有方案的權值和相當於高維字尾和。具體地,設 \(f_s = \sum\limits_{t \supseteq s} a_t\),則一個轉化後的集合 \(s\) 權值和為 \(f_s\)

現在加入 \(\texttt{0}\),由於 \(\texttt{0}\) 的個數很少,所以我們對 \(\texttt{0}\) 容斥,它可以看作 \(\texttt{?}\) 的方案數減去 \(\texttt{1}\) 的方案數,所以我們列舉每個 \(\texttt{0}\) 是變成 \(\texttt{1}\) 還是 \(\texttt{?}\),得到答案為

\[ \sum\limits_{t \subseteq s_0} f_{s_1 \cup t} (-1)^{|t|} \]

時間複雜度 \(O(2^{cnt_0})\)

對於 \(cnt_1 \le 6\)

類似地考慮只有 \(\texttt{1}\)\(\texttt{?}\) 的情況,我們將 \(\texttt{?}\) 看成 \(\texttt{1}\),設 \(g_s = \sum\limits_{t \subseteq s} a_t\),則一個轉化後的集合 \(s\) 權值和為 \(g_{A - s}\),其中 \(A\) 為全集。

還是類似地將 \(\texttt{1}\) 容斥為 \(\texttt{?}\) 的方案減去 \(\texttt{0}\) 的方案,列舉每個 \(\texttt{1}\)\(\texttt{0}\) 還是變 \(\texttt{?}\),可得答案為

\[ \sum\limits_{t \subseteq s_1} g_{A - (s_0 \cup t)}(-1)^{|t|} \]

時間複雜度 \(O(2^{cnt_1})\)


最後 \(f, g\) 分別相當於高維後、字首和,直接上 \(\text{And-FWT}\)\(\text{Or-FWT}\) 即可。

總時間複雜度 \(O(2^nn + q 2^{\lfloor \frac{n}{3} \rfloor})\)

Code
#include <iostream>

using namespace std;

const int N = 20, S = (1 << N); 

int n, q, all;
int a[S], f[S], g[S];

void FWT_And (int *a) {
  for (int k = 1; k <= all; k <<= 1) {
    for (int i = 0; i <= all; i += (k << 1)) {
      for (int j = 0; j < k; ++j) {
        a[i + j] += a[i + j + k];
      }
    }
  }
}

void FWT_Or (int *a) {
  for (int k = 1; k <= all; k <<= 1) {
    for (int i = 0; i <= all; i += (k << 1)) {
      for (int j = 0; j < k; ++j) {
        a[i + j + k] += a[i + j];
      }
    }
  }
}

int main () {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> q, all = (1 << n) - 1;
  for (int s = 0; s <= all; ++s) {
    char c;
    cin >> c;
    a[s] = f[s] = g[s] = c - '0';
  }
  FWT_And(f), FWT_Or(g);
  for (string str; q--; ) {
    cin >> str;
    int s[3] = {0, 0, 0}, cnt[3];
    for (int i = 0; i < n; ++i) {
      if (str[i] == '0') 
        s[0] |= (1 << (n - i - 1));
      else if (str[i] == '1')
        s[1] |= (1 << (n - i - 1));
      else if (str[i] == '?')
        s[2] |= (1 << (n - i - 1));
    }
    for (int i = 0; i < 3; ++i) {
      cnt[i] = __builtin_popcount(s[i]);
    }
    if (cnt[2] <= 6) {
      int ans = 0;
      for (int t = s[2]; ; t = (t - 1) & s[2]) {
        ans += a[t | s[1]];
        if (!t) break;
      }
      cout << ans << '\n';
    }
    else if (cnt[0] <= 6) {
      int ans = 0;
      for (int t = s[0]; ; t = (t - 1) & s[0]) {
        ans += f[t | s[1]] * (__builtin_popcount(t) & 1 ? -1 : 1);
        if (!t) break;
      }
      cout << ans << '\n';
    }
    else if (cnt[1] <= 6) {
      int ans = 0; 
      for (int t = s[1]; ; t = (t - 1) & s[1]) {
        ans += g[(t | s[0]) ^ all] * (__builtin_popcount(t) & 1 ? -1 : 1);
        if (!t) break;
      }
      cout << ans << '\n';
    }
  }
  return 0;
}