文件排版 题解
前言
题目链接: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; // 让 n + 1 一直转移到 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; // r 为这一行的最后一个单词
}
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; // 注意这里细节,m 指图片左侧最后一个单词
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 = 0; // 完全不放图片的行数
for (int cur = 1, k = 18; k >= 0; --k)
if (tr1[cur][k] <= n) {
non += 1 << k;
cur = tr1[cur][k];
}
++non; // 加上最后“跳出去”的一行
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;
}
总结 & 反思
遇到多次进行同一个操作,并且是在不断“跳”的过程,可以使用倍增优化。
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18384157。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。