手套(线段树+贪心)
你现在有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 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; }