JOI Open 2019 題解


題目傳送門: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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM