2016暑期集训16B手套

手套

时间限制: 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
#include
#include
#include
#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;i2*i+1]]=i;
    }
    for (int i=1;i<=2*n;i++)
    {
        a[i]=e[a[i]];
    }
    gb(1,2*n);
    cout<return 0;
}

你可能感兴趣的:(贪心-逆序对,暑假集训)