手套
时间限制: 1 Sec 内存限制: 128 MB
提交: 9 解决: 7
[提交][状态][讨论版]
题目描述
你现在有N对手套,但是你不小心把它们弄乱了,需要把它们整理一下。N对手套被一字排开,每只手套都有一个颜色,被记为0~N-1,你打算通过交换把每对手套都排在一起。由于手套比较多,你每次只能交换相邻两个手套。请你计算最少要交换几次才能把手套排整齐。
输入
输入第一行一个N,表示手套对数。
第二行有2N个整数,描述了手套的颜色。每个数都在0~N-1之间,且每个数字都会出现恰好两次。
输出
一行,包含一个数,表示最少交换次数。
样例输入
2
0 1 0 1
样例输出
1
提示
30%的数据N≤9;
60%的数据N≤1000;
100%的数据N≤200,000。
这题在考试中把我成功卡死,
我甚至开始想这个在生活中也的确有用。
也想过贪心,但我就是觉得不可能,实际上是对的!!!
贪心后就是逆序对。
30% 搜索
60% 可以采取如下贪心策略:从左到右扫描手套的序列,若当前手套的颜色为x,则找到颜色x的另一只手套(很显然在右边),将它向左交换至第一只旁边,继续扫描。
100% 按上述贪心做法,我们可以在O(N)的时间内确定最后的颜色序列。相同颜色的手套之间必然不会发生交换,于是我们就可以确定每只手套的最终位置了。问题
转化为:给出一个序列,其中的元素各不相同,求最少交换几次能变成另一个序列。将序列的元素置换一下后,这个问题可以进一步转化为最少交换几次可以将数列排好序,即求一个数列的逆序对个数,可以用归并排序或者数据结构(线段树、树状数组等)解决。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n,a[400005],c[400005],b[400005];
int d[400005],e[400005];
ll ans;
void gb(int l,int r)
{
if (l>=r) return;
int mid=(l+r)>>1;
gb(l,mid);
gb(mid+1,r);
int st=l,ed=mid+1,cnt=l;
while (st<=mid && ed<=r)
{
if (a[st]<=a[ed])
{
c[cnt]=a[st];
cnt++;
st++;
}
else
{
ans=ans+mid-st+1;
c[cnt]=a[ed];
cnt++;
ed++;
}
}
while (st<=mid)
{
c[cnt]=a[st];
cnt++;
st++;
}
while (ed<=r)
{
c[cnt]=a[ed];
cnt++;
ed++;
}
for (int i=l;i<=r;i++)
a[i]=c[i];
}
int main()
{
scanf("%d",&n);
int tmp=0;
for (int i=1;i<=2*n;i++)
{
scanf("%d",&a[i]);
if (!b[a[i]])
{
b[a[i]]=1;
d[++tmp]=a[i];
d[++tmp]=a[i];
}
}
for (int i=0;i<n;i++)
{
e[d[2*i+1]]=i;
}
for (int i=1;i<=2*n;i++)
{
a[i]=e[a[i]];
}
gb(1,2*n);
cout<<ans<<endl;
return 0;
}