Increment and Swap

Increment and Swap

AtCoder 题目的构成,大抵都是分析出特殊性质后进行维护。


考虑到两种操作交替进行不利于分析。结合两种操作自身具有的性质,得出结论:

  • 对于任意操作序列,均能找到与其等价的操作序列,其中所有 \(2\) 操作都在 \(1\) 操作前。

这里的等价是说,执行操作后的结果相同,且操作的次数相同。这意味着我们可以先用 \(+1\) 操作得到一个长度为 \(N\) 的序列 \(B\),满足 \(B_i \geq A_i\),接下来再用交换操作将 \(B\) 排序。

考虑这样操作会产生的代价。第一部分的代价显然为 \(\sum\limits_{i=1}^{n} b_i - \sum\limits_{i=1}^{n} a_i\),第二部分的代价根据经典结论,其等价于逆序对个数,也就是 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{i-1} [b_j > b_i]\)。将两者写在一起便于观察,得到 \(\sum\limits_{i=1}^{n} (b_i - a_i + \sum\limits_{j=1}^{i-1} [b_j > b_i])\),分离出与 \(B\) 有关的量,最终得到:

\[\sum\limits_{i=1}^{n} (b_i + \sum\limits_{j=1}^{i-1} [b_j > b_i]) - \sum\limits_{i=1}^{n} a_i \]


接下来我们的目标就是最小化 \(\sum\limits_{i=1}^{n} (b_i + \sum\limits_{j=1}^{i-1} [b_j > b_i])\),注意到下列结论:

  • 存在一种最优方案,使得所有的 \(b_i\) 都在序列 \(A\) 中出现。

这是很多题里常见的结论,容易从感性理解方面去进行证明。接下来就可以离散化 \(A\) 了。

考虑使用这个式子推导暴力算法,我们声称从 \(1\) 到 \(n\) 考虑所有的 \(i\),取满足 \(x + \sum\limits_{j=1}^{i-1} [b_j > x]\) 最小的 \(x\) 作为 \(b_i\),若有多个取最小的 \(x\)。这个策略是正确的,从感性理解方面,其既保证了当前的贡献最小,又尽量让后面的贡献变小。

维护数组 \(C\),初始时 \(c_i = i\)。\(C\) 数组表示了选择 \(b_i = x\) 时,产生的贡献是 \(c_x\):

  • 找到 \(\geq a_i\) 的下标中最小的 \(c\),记为 \(c_j\)。

  • 令 \(b_i = j\),则将 \([1,j-1]\) 中所有 \(c_j\) 的贡献 \(+1\)。

这个过程容易使用线段树维护,时间复杂度 \(O(n \log n)\)。

//Ad astra per aspera
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[200010],num_list[200010];
struct Data{
	int pos,num;
}INF=(Data){0x3f3f3f3f,0x3f3f3f3f};
bool operator <(const Data &lhs,const Data &rhs){
	if(lhs.num!=rhs.num){
		return lhs.num<rhs.num;
	}
	else{
		return lhs.pos<rhs.pos;
	}
}
struct Node{
	int l,r,tag;
	Data minn;
}seg[800010];
void pushup(int id){
	seg[id].minn=min(seg[id*2].minn,seg[id*2+1].minn);
}
void pushdown(int id){
	seg[id*2].minn.num+=seg[id].tag;
	seg[id*2].tag+=seg[id].tag;
	seg[id*2+1].minn.num+=seg[id].tag;
	seg[id*2+1].tag+=seg[id].tag;
	seg[id].tag=0;
}
void build(int id,int l,int r){
	seg[id].l=l;
	seg[id].r=r;
	seg[id].tag=0;
	if(l==r){
		seg[id].minn=(Data){l,num_list[l]};
	}
	else{
		int mid=(l+r)>>1;
		build(id*2,l,mid);
		build(id*2+1,mid+1,r);
		pushup(id);
	}
}
void modify(int id,int l,int r){
	if(l<=seg[id].l  &&  seg[id].r<=r){
		seg[id].minn.num++;
		seg[id].tag++;
		return ;
	}
	pushdown(id);
	if(l<=seg[id*2].r){
		modify(id*2,l,r);
	}
	if(seg[id*2+1].l<=r){
		modify(id*2+1,l,r);
	}
	pushup(id);
} 
Data query(int id,int l,int r){
	if(l<=seg[id].l  &&  seg[id].r<=r){
		return seg[id].minn;
	}
	pushdown(id);
	Data minn=INF;
	if(l<=seg[id*2].r){
		minn=min(minn,query(id*2,l,r));
	}
	if(seg[id*2+1].l<=r){
		minn=min(minn,query(id*2+1,l,r));
	}
	return minn;
}
int main(){
	int n,num_cnt=0;
	long long ans=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		ans-=a[i];
		num_list[++num_cnt]=a[i];
	}
	sort(num_list+1,num_list+num_cnt+1);
	num_cnt=unique(num_list+1,num_list+num_cnt+1)-(num_list+1);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(num_list+1,num_list+num_cnt+1,a[i])-num_list;
	}
	build(1,1,num_cnt);
	for(int i=1;i<=n;i++){
		Data b=query(1,a[i],num_cnt);
		ans+=b.num;
		if(b.pos>1){
			modify(1,1,b.pos-1);
		}
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2026-05-15 09:26  Oken喵~  阅读(22)  评论(0)    收藏  举报