[dp] [随机化] [树状数组] P8108 [Cnoi2021] 绀珠传说
posted on 2024-06-09 07:53:02 | under | source
假设让每个点都独自删去,答案是 \(n^2\),考虑合并一些操作。
抽出两列单独观察,那么发现颜色相同的点对可以合并操作,需要满足这些操作点对不相交,才一定存在一种对应方案。也就是两列的 \(\rm LCS\) 了。
发现不相邻的两列不会互相影响,也就是上述结论可以推广到整个方阵,记 \(g_i\) 表示 \(i\) 列和 \(i+1\) 列的 \(\rm LCS\) 长度,答案就是 \(n^2-\sum\limits_{i<n} g_i\)。
现在考虑怎么求 \(g\),朴素暴力 \(O(n^3)\),注意到数据均匀随机,也就是说一列内的元素几乎是个排列。换种角度考虑问题,将相邻两列的元素视作二分图的两部,那么让相邻元素连边,等价于求最大匹配。
由于二分图边数期望是 \(O(n)\) 的,可以依此设计出 \(\rm dp\),令第 \(t+1\) 列元素从底向上是 \(B_1\dots B_n\),并遍历第 \(t\) 列的元素 \(A_1\dots A_n\)。记 \(f_{j}\) 表示目前为止只向 \(B_1\dots B_j\) 连边的最大匹配数,若当前 \(A_i\) 可以向 \(B_k\) 连边,有转移 \(f_{k} = \max(f_1\dots f_{k-1})+1\)。
可以视作单点改、区间查,树状数组即可。随机数据下期望复杂度 \(O(n^2\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
int n, a[N][N], ans;
vector<int> pos[N];
namespace BIT{
int t[N];
inline void clen() {memset(t, 0, sizeof t);}
inline void add(int a, int k) {for(; a < N; a += a & -a) t[a] = max(t[a], k);}
inline int qry(int a) {int res = 0; for(; a; a -= a & -a) res = max(res, t[a]); return res;}
}using namespace BIT;
int main(){
cin >> n;
for(int i = n; i; --i)
for(int j = 1; j <= n; ++j) scanf("%d", &a[i][j]);
ans = n * n;
for(int t = 1; t < n; ++t){
clen();
for(int i = 1; i <= n; ++i) pos[i].clear();
for(int i = n; i; --i) pos[a[i][t + 1]].push_back(i);
for(int i = 1; i <= n; ++i)
for(auto v : pos[a[i][t]]) add(v, qry(v - 1) + 1);
ans -= qry(n);
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号