【题解】CF1268C K Integers
萌新不懂就问,这是什么时代的题啊???
思路
trick 题。
首先根据 trick 可知:先将 \([1, k]\) 中的数聚在一起再排序是最优的。
排序的花费是逆序对数,所以现在的问题是求把 \([1, k]\) 聚在一起的最小花费。
又根据经典 trick 可知把它们聚在中位数的位置一定是最优的。
假设中位数为 \(t\),如果 \(t\) 左边有 \(x\) 个数,那么左侧移动的花费是 \(\sum\limits_{i = 1}^x t - i + 1 - p_i = tx - \frac{x(x - 1)}{2} - \sum\limits_{i = 1}^x p_i\)
同理右侧的花费是 \(\sum\limits_{i = 1}^x p_i - tx - \frac{x(x - 1)}{2}\)
那么只需要一个树状数组维护逆序对,另一个维护一下下标和即可。
这里直接 BIT 二分的复杂度是 \(O(n \log n)\),但是我写了两只 \(\log\).
代码
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int n;
int p[maxn], a[maxn];
struct BIT
{
ll c[maxn];
int lowbit(int x) { return x & (-x); }
void update(int p, int w) { for (int i = p; i <= n; i += lowbit(i)) c[i] += w; }
ll query(int p)
{
ll res = 0;
for (int i = p; i; i -= lowbit(i)) res += c[i];
return res;
}
} c1, c2;
int main()
{
ll cur = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &p[i]);
a[p[i]] = i;
}
for (int i = 1; i <= n; i++)
{
cur += (i - 1 - c1.query(a[i]));
c1.update(a[i], 1);
c2.update(a[i], a[i]);
int l = 1, r = n;
while (l < r)
{
int mid = (l + r + 1) >> 1;
if (c1.query(mid - 1) * 2 <= i) l = mid;
else r = mid - 1;
}
ll lcnt = c1.query(l), lsum = c2.query(l);
ll rcnt = i - lcnt, rsum = c2.query(n) - lsum;
ll cost = l * lcnt - lsum - lcnt * (lcnt - 1) / 2;
cost += rsum - l * rcnt - rcnt * (rcnt + 1) / 2;
printf("%lld ", cur + cost);
}
putchar('\n');
return 0;
}