Loading

#535. 「LibreOJ Round #6」花火

#535. 「LibreOJ Round #6」花火

题意:给定一个序列,至多交换一次任意两个数,使得逆序对数量最小。

数据范围:\(n\le 5\times 10^5\)

考虑什么时候不交换,只有 \(a_i=i\) 时不交换。

\(\bullet\) 暴力做法,枚举 \(i\)\(j\) 交换,再求逆序对数。复杂度 \(O(n^3\log n)\)。预计得分 \(10\) 分。

\(\bullet\) 优化,可以预处理出 \(f_{i,j}\) 表示 \(i\) 换到 \(j\) 减少的逆序对数量,同理处理出 \(g_{j,i}\) 表示 \(j\) 往前换到 \(i\) 变化的逆序对数,答案即为 \(\min\limits_{i<j}(ans-f_{i,j}-g_{j,i}+1)\)。复杂度 \(O(n^2)\)。预计得分 \(40\) 分。

\(\bullet\) 首先,我们交换的两数一定是 \(i<j\)\(a_i>a_j\),否则肯定不优,再观察交换两个数时变化的逆序对数量,发现为

\(1+2\times \sum\limits_{i<k<j}[a_j<a_k<a_i]\)

我们要使这部分最大,后面的部分实际上就是二维数点,求左上角 \((i,a_i)\),右下角 \((j,a_j)\) 的矩形中有多少点。

对于二维数点,有两种基本方法:考虑前缀和的思想,将询问拆成四部分计算贡献;考虑扫描线,先维护一位的偏序,再用线段树维护另一维。

考虑扫描线,预处理出所有矩形,依次计算贡献,复杂度 \(O(n\log n+逆序对数\times\log n)\)。预计得分没之前高。

\(\bullet\) 我们贪心发现,假如 \(k<i\)\(a_k>a_i\),我们完全可以把 \(i\) 换成 \(k\),让答案变得更优。同理 \(j\) 类似。

换言之,我们所选的 \(i\)\(j\) 一定分别是前缀最大值和后缀最小值。可以预处理出两部分,复杂度变成 \(O(n\log n+前缀最大值数量\times 后缀最小值数量\times\log n)\)。在随机数据下,两者数量接近 \(\log n\),预计得分 \(80\) 分。

\(\bullet\) 我们发现瓶颈在于矩形数量过多,无法全部求出所有最大值。我们考虑反过来统计贡献,对于点 \((k,a_k)\),它能够贡献到哪些矩形中。应满足以下条件:

\(\begin{cases}i<k\\a_i>a_k\\j>k\\a_j<a_k\end{cases}\)

发现在前缀最大值序列中,\(k\) 影响的是一段区间;在后缀最大值中也是一段区间。若我们将前缀最大值和后缀最小值序列分别作为 \(y\) 轴和 \(x\) 轴,那么实际上就是对一个矩形加 \(1\) 的操作。求出哪个点权值最大,此时它影响的逆序对最多。需要的操作为区间加 \(1\),全局求最大值。同样可以用扫描线实现。复杂度 \(O(n\log n)\)。预计得分 \(100\) 分。

总结:对于一个操作,我们可以尝试表示出其影响其他值的条件。

考虑贪心的减少交换方案数,发现所选 \(i\)\(j\)\(a_i\)\(a_j\) 递增。

对于二维数点的形式,可以考虑用扫描线求出所有矩形的贡献;同时也可以反过来考虑,求一个点在哪些矩形内,若此时矩形的两维都是一段区间(与前面贪心联系),我们以此作为坐标轴的两维,枚举每个点,维护它能贡献哪些矩形,同样是扫描线的形式。

#include <bits/stdc++.h>
typedef long long ll;
ll read() {
	ll x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	} 
	return x * f;
}
ll T, o, n, ans;
ll a[500010], c[500010];
ll lowbit(ll x) {return x & (-x);}
void update(ll x) {
	for(ll i = x; i <= n; i += lowbit(i)) {
		c[i]++; 
	}
}
ll query(ll x) {
	ll ret = 0;
	for(ll i = x; i; i -= lowbit(i)) {
		ret += c[i];
	}
	return ret;
}
struct node {
	ll l, r, y, k;
} lne[1000010];
ll tot;
bool cmp(node a, node b) {
	if(a.y != b.y) return a.y < b.y;
	return a.k < b.k;
}
ll lazy[500010 << 2];
struct tr {
	ll max;
} t[500010 << 2];
void pushup(ll u) {t[u].max = std::max(t[u << 1].max, t[u << 1 | 1].max);}
void pushdown(ll u) {
	if(!lazy[u]) return;
	t[u << 1].max += lazy[u];
	t[u << 1 | 1].max += lazy[u];
	lazy[u << 1] += lazy[u], lazy[u << 1 | 1] += lazy[u];
	lazy[u] = 0;
}
void modify(ll u, ll l, ll r, ll L, ll R, ll k) {
	if(L <= l && r <= R) {
		t[u].max += k;
		lazy[u] += k;
		return;
	}
	ll mid = (l + r) >> 1;
	pushdown(u);
	if(L <= mid) modify(u << 1, l, mid, L, R, k);
	if(R > mid) modify(u << 1 | 1, mid + 1, r, L, R, k);
	pushup(u);
}
bool vis[500010];
ll st1[500010], st2[500010];
ll top1, top2;
ll binary(ll x) {
	ll l = 1, r = top1, ret = top1;
	while(l <= r) {
		ll mid = (l + r) >> 1;
		if(a[st1[mid]] >= a[x]) r = mid - 1, ret = mid;
		else l = mid + 1;
	}
	return st1[ret];
}
ll binary2(ll x) {
	ll l = 1, r = top2, ret = 1;
	while(l <= r) {
		ll mid = (l + r) >> 1;
		if(a[st2[mid]] <= a[x]) r = mid - 1, ret = mid;
		else l = mid + 1;
	}
	return st2[ret];
}
void Solve() {
		bool flg = 0;
		n = read();
		for(ll i = 1; i <= n; i++) {
			a[i] = read();
			if(a[i] != i) flg = 1;
		}
		if(!flg) {
			std::cout << "0\n";
			return;
		} 
		for(ll i = n; i >= 1; i--) {
			ans += query(a[i]);
			update(a[i]);
		}
		for(ll i = 1; i <= n; i++) {
			if(i == 1 || a[i] > a[st1[top1]]) st1[++top1] = i, vis[i] = 1; 
		}
		for(ll i = n; i >= 1; i--) {
			if(i == n || a[i] < a[st2[top2]]) st2[++top2] = i, vis[i] = 1;
		} 
		for(ll i = 1; i <= n; i++) {
			if(vis[i]) continue;
			ll l = binary(i), r = binary2(i);
			// std::cout << l << " " << i << " " << r << "\n";
			if(l < i && i < r) {
				lne[++tot] = {i + 1, r, l, 1};
				lne[++tot] = {i + 1, r, i, -1};
			}
		}
		std::sort(lne + 1, lne + tot + 1, cmp);
		ll ret = 0;
		for(ll i = 1; i <= tot; i++) {
			modify(1, 1, n, lne[i].l, lne[i].r, lne[i].k);
			if(lne[i].y != lne[i + 1].y) ret = std::max(ret, t[1].max);
		}	
		// std::cout << ret << "\n";
		std::cout << ans - 2 * ret << "\n";

}

int main() {
  
	Solve();

	return 0;
}
posted @ 2024-07-20 20:34  Fire_Raku  阅读(9)  评论(0编辑  收藏  举报