題目傳送門:LOJ「JOI Open 2019」。
三級跳 / 三段跳び / Triple Jump
考慮一組合法的 \(a, b, c\),如果在 \(a, b\) 之間存在一個下標 \(i\) 滿足 \(A_i \ge A_a\) 或 \(A_i \ge A_b\),則顯然選擇 \(i\) 不更劣。
也就是說,如果只考慮這樣的 \(a, b\):滿足它們之間的數都小於 \(\min(A_a, A_b)\);答案也不會改變。
可以發現這樣的 \(a, b\) 只有 \(\mathcal O (N)\) 組,也就是對於每個點來說,左邊和右邊第一個大於等於本身的數。
這 \(\mathcal O (N)\) 組 \(a, b\) 可以通過單調棧求出。
對於詢問,考慮離線,按照左端點從大到小排序。把那些可行的 \(a, b\) 也記錄在左端點(\(a\))上。
在進行掃描線的同時,對於每個右端點,維護一個 \(B\) 值,表示如果選它作為 \(c\),最大的 \(A_a + A_b\) 的值。
在處理詢問前先加入這些新的 \(a, b\),相當於把所有在 \(2 b - a\) 右側的位置的 \(B\) 值與 \(A_a + A_b\) 取 \(\max\)。
詢問時則是詢問 \([l + 2, r]\) 之間的所有位置的最大的 \(B_i + A_i\) 之和。
因為 \(B\) 只會變大,可以用線段樹維護區間中最大的 \(A_i\) 以及最大的 \(B_i + A_i\),使用一個標記下傳對 \(B\) 的取 \(\max\) 操作即可。
代碼如下,時間復雜度為 \(\mathcal O ((N + Q) \log N)\):
#include <cstdio>
#include <algorithm>
#include <vector>
typedef long long LL;
const int Inf = 0x3f3f3f3f;
const int MN = 500005, MQ = 500005, MS = 1 << 20 | 7;
void chkmx(LL &x, LL y) { x = x < y ? y : x; }
int N, Q;
LL A[MN];
int stk[MN], tp;
std::vector<int> V[MN], W[MN];
int qr[MQ]; LL Ans[MQ];
#define li (i << 1)
#define ri (li | 1)
#define mid ((l + r) >> 1)
#define ls li, l, mid
#define rs ri, mid + 1, r
LL v2[MS], v3[MS], tg[MS];
inline void P(int i, LL x) { chkmx(tg[i], x), chkmx(v3[i], x + v2[i]); }
inline void Pushdown(int i) { if (tg[i]) P(li, tg[i]), P(ri, tg[i]), tg[i] = 0; }
void Build(int i, int l, int r) {
if (l == r) return v2[i] = v3[i] = A[l], void();
Build(ls), Build(rs);
v2[i] = v3[i] = std::max(v2[li], v2[ri]);
}
void Mdf(int i, int l, int r, int a, int b, LL x) {
if (r < a || b < l) return ;
if (a <= l && r <= b) return P(i, x);
Pushdown(i), Mdf(ls, a, b, x), Mdf(rs, a, b, x);
v3[i] = std::max(v3[li], v3[ri]);
}
LL Qur(int i, int l, int r, int a, int b) {
if (r < a || b < l) return 0;
if (a <= l && r <= b) return v3[i];
Pushdown(i);
return std::max(Qur(ls, a, b), Qur(rs, a, b));
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%lld", &A[i]);
A[stk[tp = 1] = 0] = Inf;
for (int i = 1; i <= N; ++i) {
while (A[stk[tp]] < A[i]) V[stk[tp--]].push_back(i);
if (stk[tp]) V[stk[tp]].push_back(i);
stk[++tp] = i;
} while (tp) --tp;
scanf("%d", &Q);
for (int i = 1, x; i <= Q; ++i) scanf("%d%d", &x, &qr[i]), W[x].push_back(i);
Build(1, 1, N);
for (int i = N - 2; i >= 1; --i) {
for (int j : V[i]) if (j + j - i <= N) Mdf(1, 1, N, j + j - i, N, A[i] + A[j]);
for (int j : W[i]) Ans[j] = Qur(1, 1, N, i + 2, qr[j]);
}
for (int i = 1; i <= Q; ++i) printf("%lld\n", Ans[i]);
return 0;
}
匯款 / 送金 / Remittance
匯款的系數矩陣滿秩,通過高斯消元(稀疏矩陣)解出每個房子要向下一個房子匯多少錢,在模意義下計算可以防止浮點數。
如果解出來是小數或者負數,一定不可能,求出模意義下的數值后這可以很好地判斷。
如果初始錢數非零,但是最終總錢數為零,也不可能。
否則一定可行。
證明?不知道。反正其他做法(就是模擬 \(\log N\) 輪的做法)也沒證明。
下面是代碼,時間復雜度為 \(\mathcal O (N + \log \mathrm{MOD})\):
#include <cstdio>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 1000005;
inline int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
inline int gInv(int b) { return qPow(b, Mod - 2); }
int N, A[MN], B[MN], X[MN];
int xk[MN], xb[MN];
int ok;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d%d", &A[i], &B[i]);
ok = 1;
for (int i = 1; i <= N; ++i) if (A[i] != B[i]) ok = 0;
if (ok) return puts("Yes"), 0;
xk[N] = 1, xb[N] = 0;
for (int i = N - 1; i >= 1; --i) {
xk[i] = 2 * xk[i + 1];
if (xk[i] >= Mod) xk[i] -= Mod;
xb[i] = (2ll * xb[i + 1] + B[i + 1] - A[i + 1] + Mod) % Mod;
}
X[N] = (2ll * xb[1] + B[1] - A[1] + Mod) * gInv((1 + 2 * (Mod - xk[1])) % Mod) % Mod;
for (int i = 1; i < N; ++i) X[i] = ((LL)xk[i] * X[N] + xb[i]) % Mod;
ok = 1;
for (int i = 1; i <= N; ++i)
if (X[(i + N - 2) % N + 1] - 2ll * X[i] != B[i] - A[i]) ok = 0;
if (!ok) return puts("No"), 0;
ok = 0;
for (int i = 1; i <= N; ++i) if (B[i]) ok = 1;
if (!ok) return puts("No"), 0;
puts("Yes");
return 0;
}
病毒實驗 / ウイルス実験 / Virus Experiment
假定我們已知任意兩個格子之間的關系:如果僅 \(u\) 感染了病毒,那么 \(v\) 在若干時刻后也會被感染 / 永遠不會被感染。
顯然這個圖強連通分量縮點之后就是一個 DAG,我們需要考慮所有的無出度的強連通分量的大小。
然而我們並不已知這些關系,也無法存儲。那么我們到底能夠求什么?
我們可以寫一個 BFS 來求出如果初始某格子被感染,最終會感染到哪些格子,這是如何做到的呢:
-
首先,一個格子是否會被感染,僅和它上下左右的格子是否會被感染有關。
-
我們用 \(16 \times \mathcal O (M)\) 的時間,預處理出上下左右四個格子是否被感染共 \(2^4 = 16\) 種情況下,被有病毒的風吹的最長時間。
那么每次拓展一個新格子的時候,就可以在 \(\mathcal O (1)\) 的時間內確定它周圍的格子目前是否會被感染。
這樣在每個點運行一遍 BFS 就可以得到 \(\mathcal O (R^2 C^2)\) 的時間復雜度。
考慮這樣一個事實:如果初始 \(u\) 感染了,可以讓 \(v\) 也感染,則 \(u\) 導致的感染人數一定不少於 \(v\) 導致的感染人數。
而且再考慮到這個關系是有傳遞性的:如果 \(u \to v\) 並且 \(v \to w\),那么一定有 \(u \to w\)。
這提示我們不一定每次運行 BFS 都需要運行“滿”(不能再拓展新點),可以在某些條件下中斷,以減少復雜度。
考慮維護這樣的一些區域:每個區域中有一個關鍵點,表示從這個區域內的任何一點出發,都能到達這個關鍵點。
一開始每個格子自成一個區域,然后這些區域會逐漸合並:
我們任取一個區域,從它的關鍵點出發 BFS,一旦到達了一個不在本區域內的格子,就把該區域並入到達的區域中。
可以發現這樣的過程是滿足區域的性質的,但是復雜度仍然不變,考慮優化。
考慮求最小生成樹的 Boruvka 算法,它保證每層的時間是 \(\mathcal O (n)\) 的,並且讓連通塊個數除以 \(2\),所以復雜度為 \(\mathcal O (n \log n)\)。
把這個思想運用到本題中:枚舉每個區域,如果 BFS 到了新區域就合並,但是如果該區域已經被合並過了,就不進行 BFS 了。
可以發現,每次從一個區域進行 BFS 的時間復雜度為 \(\mathcal O (\mathrm{size})\),其中 \(\mathrm{size}\) 為該區域大小(因為只要找到了新格子就立刻退出)。
這樣可以保證每層的總時間復雜度不會超過 \(\mathcal O (RC)\)。
並且也最多執行 \(\mathcal O (\log(RC))\) 層,這是因為:
如果某次 BFS 沒有到達任何新區域,則它一定對應了 DAG 中沒有出度的一個連通分量。
對於其它區域,它們要么找到一個還沒被合並過的區域,這兩個區域在這一層都不能用了,或者找到一個已經被合並過的區域。
前者讓 \(2\) 個區域變成 \(1\) 個,后者讓 \(1\) 個區域變成 \(0\) 個。
所以無論如何,每一層至少少掉一半的區域(除了對應最后的無出度的連通分量的區域),最多執行 \(\mathcal O (\log (RC))\) 層。
下面是代碼,時間復雜度為 \(\mathcal O (M + RC \log(RC))\):
#include <cstdio>
#include <algorithm>
const int MK = 100005, MN = 805, MS = 640005;
const char dir[5] = "NSWE";
const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int K, S[MK * 2], N, M, A[MN][MN], tim[16];
void Init() {
static char Str[MK];
scanf("%d%d%d%s", &K, &N, &M, Str + 1);
for (int i = 1; i <= K; ++i)
for (int j = 0; j < 4; ++j)
if (Str[i] == dir[j]) S[i] = S[K + i] = j;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
scanf("%d", &A[i][j]);
if (A[i][j] == 0) A[i][j] = 100001;
}
for (int d = 1; d < 16; ++d) {
int lst = 0, mxl = 0;
for (int i = 1; i <= 2 * K; ++i) {
if (d >> S[i] & 1) mxl = std::max(mxl, ++lst);
else lst = 0;
}
if (mxl == 2 * K) mxl = 100000;
tim[d] = mxl;
}
}
int bx[MN][MN], by[MN][MN], tx[MN][MN], ty[MN][MN];
int col[MN][MN], tot, qx[MS], qy[MS], lb, rb;
inline bool chk(int x, int y) {
int stat = 0;
for (int d = 0; d < 4; ++d) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 1 || nx > N || ny < 1 || ny > M) continue;
if (col[nx][ny] == tot) stat |= 1 << d;
}
return A[x][y] <= tim[stat];
}
inline bool BFS(int sx, int sy) {
if (A[sx][sy] > 100000) {
tx[sx][sy] = sx;
ty[sx][sy] = sy;
rb = N * M + 1;
return 0;
}
col[sx][sy] = ++tot;
lb = rb = 1, qx[1] = sx, qy[1] = sy;
while (lb <= rb) {
int x = qx[lb], y = qy[lb]; ++lb;
for (int d = 0; d < 4; ++d) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 1 || nx > N || ny < 1 || ny > M) continue;
if (col[nx][ny] == tot) continue;
if (!chk(nx, ny)) continue;
int nbx = bx[nx][ny], nby = by[nx][ny];
if (nbx != sx || nby != sy) {
int ntx = tx[nbx][nby], nty = ty[nbx][nby];
if (ntx) {
tx[sx][sy] = ntx, ty[sx][sy] = nty;
return 1;
}
tx[sx][sy] = tx[nbx][nby] = nbx;
ty[sx][sy] = ty[nbx][nby] = nby;
return 1;
}
col[nx][ny] = tot;
++rb, qx[rb] = nx, qy[rb] = ny;
}
}
tx[sx][sy] = sx;
ty[sx][sy] = sy;
return 0;
}
int ans, cnt;
int main() {
Init();
ans = N * M + 1;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
bx[i][j] = i, by[i][j] = j;
while (1) {
int ok = 0;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
tx[i][j] = ty[i][j] = 0;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
int x = bx[i][j], y = by[i][j];
if (tx[x][y]) continue;
int ret = BFS(x, y);
if (ret) ok = 1;
}
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
int bi = bx[i][j], bj = by[i][j];
bx[i][j] = tx[bi][bj];
by[i][j] = ty[bi][bj];
}
if (!ok) break;
}
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
tx[i][j] = ty[i][j] = 0;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j) {
int x = bx[i][j], y = by[i][j];
if (tx[x][y]) continue;
BFS(x, y);
if (ans > rb) cnt = ans = rb;
else if (ans == rb) cnt += rb;
}
printf("%d\n%d\n", ans, cnt);
return 0;
}