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;
}

浙公网安备 33010602011771号