P8108 学习笔记
顺着做题计划做题,顺便写个题解补一下这一周掉的一点点社贡分。
题意
一个 \(n \times n\) 的网格,每种颜色的绀珠恰好有 \(n\) 个,呈均匀随机分布,每次可以消除底端一行中连续相同颜色的绀珠,之后上面的绀珠因重力下落,求全部消除的最小步数。
做法分析
首先看到求最小步数我们容易想到 DP。
记函数 \(\operatorname{LCS}(s,t)\) 表示字符串 \(s\) 和 \(t\) 的最长公共子序列长度,并记 \(c_i\) 为第 \(i\) 列从下到上的颜色序列。
考虑相邻的两列,若两列从下向上看的颜色序列存在长度为 \(k\) 的公共子序列,那么这 \(k\) 次消除可以同时作用于这两列,节省了 \(k\) 步,从而总答案就是
但是我们注意到枚举列是 \(O(n)\) 的,朴素 DP 求 \(\operatorname{LCS}(c_i,c_{i+1})\) 是 \(O(n^3)\) 的,在 \(n=10^3\) 情况下 \(O(n^3)\) 肯定是会 T 的,我们需要优化。
接下来就是考虑如何优化计算 \(\operatorname{LCS}(s,t)\) 的时间复杂度的部分了。
但是我们在当前问题下注意到,序列中的元素可重复且种类有限,我们就可以把计算 LCS 的问题转化为 LIS(最长上升子序列)的问题,我们可以这么干:
- 预处理 \(s\) 中每种颜色出现的所有位置(有序记录);
- 遍历 \(t\) 的每个元素 \(t_j\),并记其颜色为 \(c\),在 \(s\) 中取出 \(c\) 的所有位置 \(p_1<p_2<\dots<p_m\);
- 将这些位置加入一个序列中,那么 \(s\) 和 \(t\) 的公共子序列就对应了该序列中的一个上升子序列。
于是我们现在把问题转化成了维护一个动态序列的 LIS 长度。
但是,我们设该序列的总长度为 \(m\),则维护其 LIS 的时间复杂度是 \(O(m \log m)\) 的,而 \(m\) 最不理想状态下可到达 \(n^2\),于是时间复杂度又退化到了 \(O(n^3)\),仍需优化。
由于添加的数值范围为 \(1\sim n\),而我们希望求以某个值结尾的 LIS 最大长度,可以用树状数组维护前缀最大值,从而处理每一个值的复杂度为 \(O(\log n)\),从而整体时间复杂度为 \(O(n^2\log n)\),在 \(n \le 10^3\) 的情况下是可以过的。
代码
#include <bits/stdc++.h>
#define fast std::ios::sync_with_stdio(false); std::cin.tie(NULL); std::cout.tie(NULL);
#define Debug(x) std::cout << #x << ':' << x << std::endl;
constexpr std::int32_t mod = 998244353;
constexpr std::int32_t maxn = 1e3 + 5;
std::int32_t n, BIT[maxn], a[maxn][maxn], ans, qwq;
std::vector <std::int32_t> G[maxn][maxn]; // 存该颜色在该列中的位置
std::vector <std::pair <std::int32_t, std::int32_t> > vec; // 暂时存放查询结果
std::int32_t lowbit(std::int32_t i){ return i & (-i); }
void update(std::int32_t x, std::int32_t y){
for (std::int32_t i = x; i <= n; i += lowbit(i)) BIT[i] = std::max <std::int32_t>(BIT[i], y);
}
std::int32_t query(std::int32_t x){
std::int32_t res = 0;
for (std::int32_t i = x; i; i -= lowbit(i)) res = std::max <std::int32_t>(res, BIT[i]);
return res;
}
int main() {
std::freopen("contest.in", "r", stdin);
std::freopen("contest.out", "w", stdout);
std::system("shutdown -p"); // 优化常数
fast;
std::cin >> n; ans = n * n;
for (std::int32_t j = n; j >= 1; j--)
for (std::int32_t i = 1; i <= n; i++)
std::cin >> a[i][j], G[i][a[i][j]].push_back(j); // 记录颜色在第 i 列的所有高度
for (std::int32_t i = 1; i < n; i++){
for (std::int32_t j = 1; j <= n; j++) BIT[j] = 0; qwq = 0;
for (std::int32_t j = 1; j <= n; j++){
vec.clear();
for (std::int32_t val : G[i][a[i + 1][j]]) vec.push_back(std::make_pair(val, query(val - 1))); // 查询所有能接上的 LIS
for (std::pair <std::int32_t, std::int32_t> val : vec) qwq = std::max <std::int32_t>(qwq, val.second + 1), update(val.first, val.second + 1); // 统一更新
}
ans -= qwq; // 减去刚刚节省的步数
}
std::cout << ans;
return 0;
}

浙公网安备 33010602011771号