手套(线段树+贪心)

你现在有N对手套,但是你不小心把它们弄乱了,需要把它们整理一下。N对手套被一字排开,每只手套都有一个颜色,被记为0~N-1,你打算通过交换把每对手套都排在一起。由于手套比较多,你每次只能交换相邻两个手套。请你计算最少要交换几次才能把手套排整齐(只需要手套配对,不需要手套按从小到大的编号排序)。

30%的数据N≤9;
60%的数据N≤1000;
100%的数据N≤200,000。

输入格式 

输入第一行一个N,表示手套对数。
第二行有2N个整数,描述了手套的颜色。每个数都在0~N-1之间,且每个数字都会出现恰好两次。

输出格式 

一行,包含一个数,表示最少交换次数。

输入样例 

2
1 0 1 0

输出样例 

1

将中间两个手套交换过来,颜色序列变成1 1 0 0。

题解:我们考虑第1只手套,和他配对的是第a只,我们把右边的移到最左边总是最优的,如果不是右边移到最左边,就可能会出现另一对手套配对时要跨过当前的手套,而我们把右边的移到最左边就可以避免这种情况。所以我们每次把右边的手套移到左边,但是我们又不能用数组去模拟,那么怎么知道右边的手套移动了多少次呢?

假如枚举到左边的手套在a,且未完成配对,右边的手套在b,则需要移动的次数为(b-a-1)再减去中间已经移到左边的手套数,对于中间移走的手套数,我们考虑用线段树维护,以下标为关键字,1表示移走,求个区间和就可以了。

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<fstream>
#include<iostream>
#include<cstring>
using namespace std;

int n,a[400050],id[400020],tree[4500000],vis[400020];
long long ans;

void Updata(int root,int l,int r,int wei,int x)
{
    if (l==r&&r==wei)
    {
        tree[root]+=x;
        return;
    }
    int mid=(l+r)/2;
    if (wei<=mid) Updata(root*2,l,mid,wei,x);
    else Updata(root*2+1,mid+1,r,wei,x);
    tree[root] = tree[root*2]+tree[root*2+1];
}

int Query(int root,int l,int r,int L,int R)
{
    if (L<=l&&r<=R) return tree[root];
    if (R<l||L>r) return 0;
    int mid=(l+r)/2;
    return Query(root*2,l,mid,L,R)+Query(root*2+1,mid+1,r,L,R);
}

int main()
{
    freopen("2067.in","r",stdin);
    freopen("2067.out","w",stdout);
    scanf("%d",&n);
    for (int i=1; i<=2*n; i++)
    {
        scanf("%d",&a[i]);
        id[a[i]] = i;
    }
    for (int i=1; i<=2*n; i++)
    Updata(1,1,2*n,i,1);
    for (int i=1; i<=2*n; i++)
    if (vis[a[i]]==0)
    {
        ans = ans+Query(1,1,2*n,i+1,id[a[i]]-1);
        vis[a[i]]=1;
        Updata(1,1,2*n,id[a[i]],-1);
    }
    printf("%lld\n",ans);
    return 0;
} 

 

posted @ 2017-09-28 21:55  最终惊吓者——Janous  阅读(252)  评论(0编辑  收藏  举报