容易证明一定存在一个最优形态,满足存在两个位置 ,使得执行的操作形如:
- 把 这些书按顺序移动到最左侧。
- 把 这些书按顺序移动到最右侧。
- 范围内的书全部相邻项交换排序。
因此贡献即为: 也就是最大化 的值,其中 表示 序列的逆序对的数量。
(注:这里的 是 的逆排列,即 )。
考虑固定右端点 ,记 集合内存储了所有左端点 满足 且对于所有位置 ,都有 。
此时把 按照左端点 的位置从大到小排序(下标从 开始),则有一个关键结论:。为了方便,后面记 的值为 。下面给出该结论的证明:
:::success[证明] 要证明的结论为:。
套路的考虑对临项做差,得到 ,其中 的值为 中比 小的元素的数量。因为 所以显然有 。
因为 集合内只有对应 值严格变大的位置,因此一定有 。而又因为有 ,因此 ,命题得证。 :::
因此最大化 即 的最大值其实就是 ,因此只需要对每个右端点 求出 的值即可。
直接求 是困难的,因此可以想到通过 递推出 。显然此时有 。若不考虑逆序对的影响则有 ,但是若有 则 都会受到这组逆序对的影响,导致这些 值全部减去 。
考虑维护该事件对 的影响。因为在考虑逆序对影响之前有 ,现在对左端点 的 对应的 值减少 ,找出最大的位置 满足 是集合中最大的满足 的位置,则此时 对应的 值不会变化, 对应的 值都会减少 。因此此时只有 一个位置对应的 值不再是前缀 位置,在集合中二分出这个位置并将其删除即可。
容易想到用 set 来维护这个结构,每次从 扩展到 只需要先在集合加入 的影响,然后再二分出 的位置并将 从集合中删除。显然该算法流程的时间复杂度为 ,可以通过该题。
namespace Loyalty
{
int a[N], p[N];
inline void init() {}
inline void main([[maybe_unused]] int _ca, [[maybe_unused]] int _atc)
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> p[i], a[p[i]] = i;
set<int> se;
set<pair<int, int>> se2;
// se2 内存储的是所有将来仍然可能删除元素的位置
se.emplace(1), se2.emplace(a[1], 1);
int res = n - 1;
for (int i = 2; i <= n; ++i)
{
se.emplace(i);
vector<int> v;
auto it = se2.upper_bound({a[i], n + 1});
while (it != se2.end())
v.emplace_back(it->second), ++it;
vector<int> remov;
for (int &j : v)
{
auto it = se.upper_bound(j);
if (it == se.begin())
remov.emplace_back(j);
else
se.erase(prev(it));
}
for (int &j : remov)
se2.erase({a[j], j});
se2.emplace(a[i], i);
res = min(res, n - (int)se.size());
}
cout << res << '\n';
}
} // namespace Loyalty
喜欢的话,留下你的评论吧~