P5749 [IOI2019] 排列鞋子

算是一种新思路吧。

题目要求我们求最少的对调次数,想到了什么?求逆序对个数,我们只需将原来的 \(S_i\) 数组转化一下,求其逆序对个数即可。

转化规则为:从头开始,对于每个还未被赋值的 \(S_i\) 找在它后面离它最近的并于其符号相反的 \(S_i\)。将 \(sum+2\)(其初值为 \(0\)),其中的正数赋值为 \(sum\),负数赋值为 \(sum-1\)

例如样例一,赋值完毕的数组为:2 4 3 1

样例二为 1 2 4 3 5 6

正确性的证明:因为每次找的都是最近的 \(S_i\),所以总交换次数最少。而数组的重新排列保证了左右两只鞋子分配到的数字一定是 \(sum-1\)\(sum\)。此时问题就变成了求将一个无序的序列转化为有序,最少需要几次交换,求逆序对个数即可。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+7;
map<int,list<int> > m;
int a[N],tr[N],s[N];
bitset<N> vis;
int n;
void upd(int w,int x){
	for(int i=w;i<=n;i+=(i&-i)) tr[i]+=x;
}
int query(int x){
	int ans=0;
	while(x>0){
		ans+=tr[x];
		x-=(x&-x);
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	n*=2;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		m[s[i]].push_back(i);
	}
	for(int i=1,sum=0;i<=n;i++){
		if(vis[i]) continue;
		int j=m[-s[i]].front();//找在它后面离它最近的并于其符号相反的 Si.
		m[s[i]].pop_front();
		m[s[j]].pop_front();
		vis[i]=vis[j]=1;
		sum+=2;
		if(s[i]<0) a[i]=sum-1,a[j]=sum;
		else a[j]=sum-1,a[i]=sum;
	}
	//树状数组求逆序对。
	long long ans=0;
	for(int i=1;i<=n;i++) {
		upd(a[i],1);
		ans+=i-query(a[i]);
		cout<<a[i]<<' ';
	}
	cout<<ans;
	return 0;
}
posted @ 2024-02-09 10:02  shoot_down  阅读(44)  评论(0)    收藏  举报  来源