檔案排版 題解

XuYueming發表於2024-08-28

前言

題目連結:HDU

題意簡述

\(n\) 個單詞和一張圖片排版。每個單詞長度為 \(a_i\)。圖片佔行不確定,但是佔列始終為 \([dw + 1, dw + pw]\)。排版寬度為 \(W\),高度無限制。要求單詞間有長度為 \(1\) 的空格,單詞不能超出寬度 \(W\),不能覆蓋在圖片上,單詞間相對順序不能發生改變。有 \(Q\) 次詢問,給出 \(x_i, h_i\) 表示圖片佔行為 \([x_i + 1, x_i + h_i]\),求出在此基礎上有多少行存在圖片或字元。

\(n, W, Q \leq 10^5\)

題目分析

每次詢問如果直接 \(\Theta(n)\) 掃一遍肯定會超時,那我們要做的就是最佳化加速這一過程。

我們發現,對於一行來說,只會有被圖片覆蓋或者不被圖片覆蓋兩種狀態,並且由於圖片佔行固定,那麼所有行的這兩種狀態都是確定的。每一次掃過一行都要重新計算,無疑就是冗餘的了。

我們可以預處理出 \(f_i, g_i\) 表示以第 \(i\) 個單詞為開頭的行,這一行有或沒有被圖片佔,下一行的開始是什麼。至於如何預處理,使用雙指標即可。

這樣,我們時間複雜度就取決於排版的行數了。還是不夠。發現我們排版經歷的過程為三個大步驟:沒被圖片覆蓋、被圖片覆蓋、沒被圖片覆蓋。在同一個步驟,我們不斷讓 \(i \gets f_i\),做的是同一件事,並且是一個轉移的過程,所以很容易想到使用倍增最佳化。

如此,我們最終時間複雜度就是:\(\Theta(Q \log n)\) 的。

注意有些情況需要特判,在程式碼中標出了。

程式碼

#include <cstdio>
#include <iostream>
using namespace std;

int n, W, pw, lw, rw, q;

int tr1[100010][19], tr2[100010][19];
//  沒有放圖片        放置了圖片
long long len[100010];

void solve() {
	scanf("%d%d%d%d", &n, &W, &pw, &lw), rw = W - pw - lw;
	for (int i = 1; i <= n; ++i) {
		scanf("%lld", &len[i]);
		len[i] += len[i - 1];  // 字首和
	}
	tr1[n + 1][0] = tr2[n + 1][0] = n + 1;
	for (int l = 1, r = 1; l <= n; ++l) {
		while (r + 1 <= n && len[r + 1] - len[l - 1] + r + 1 - l <= W) ++r;
		tr1[l][0] = r + 1;
	}
	for (int l = 1, m = 0, r = 0; l <= n; ++l) {
		while (m + 1 <= n && len[m + 1] - len[l - 1] + m + 1 - l <= lw) ++m;
		r = max(r, m);
		while (r + 1 <= n && len[r + 1] - len[m] + r - m <= rw) ++r;
		tr2[l][0] = r + 1;
	}
	for (int k = 1; k <= 18; ++k) {
		for (int i = 1; i <= n + 1; ++i) {
			tr1[i][k] = tr1[tr1[i][k - 1]][k - 1];
			tr2[i][k] = tr2[tr2[i][k - 1]][k - 1];
		}
	}
	int non = 1;  // 完全不放圖片的行數
	for (int cur = 1, k = 18; k >= 0; --k)
		if (tr1[cur][k] <= n) {
			non += 1 << k;
			cur = tr1[cur][k];
		}
	scanf("%d", &q);
	for (int x, h; q--; ) {
		scanf("%d%d", &x, &h), --x;
		if (x >= non) {  // 說明圖片和文字間有空行,做特判
			printf("%d\n", non + h);
			continue;
		}
		int cur = 1, ans = x + h;
		for (int k = 18; k >= 0; --k)
			if ((1 << k) <= x)  // 先把圖片之前的單詞放了
				cur = tr1[cur][k], x -= 1 << k;
		for (int k = 18; k >= 0; --k)
			if ((1 << k) <= h)  // 現在做被圖片佔了的行的轉移
				cur = tr2[cur][k], h -= 1 << k;
		if (cur > n) {  // 放完了
			printf("%d\n", ans);
			continue;
		}
		for (int k = 18; k >= 0; --k)
			if (tr1[cur][k] <= n)  // 否則就繼續放圖片,直到放完
                ans += 1 << k, cur = tr1[cur][k];
		printf("%d\n", ans + 1);  // 加上最後跳出的那一步
	}
}

signed main() {
	int t; scanf("%d", &t);
	while (t--) solve();
	return 0;
}

總結 & 反思

遇到多次進行同一個操作,並且是在不斷“跳”的過程,可以使用倍增最佳化。

相關文章