Gym - 102082G What Goes Up Must Come Down (树状数组+贪心)

题意:有一个长度为n的序列,你每次可以选择两个相邻的元素交换,求把这个序列排成单峰序列的最少交换次数。

方法一:将元素按数值从大到小排序(保存原来的位置),把最大的插在中间,剩下的依次往两边放,依次考虑每个数该放在左边还是右边,只考虑后加入的数对已有的数的贡献。由于前面加入的数的次序对后加入的数无影响,因此贪心地选择贡献小的一边就行了。贡献为重排后下标的逆序数,注意大小相同的要特殊处理一下就行了。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N=1e5+10;
 5 struct D {
 6     int x,y;
 7     bool operator<(const D& b)const {return x>b.x;}
 8 } a[N];
 9 int c[N],n;
10 int lb(int x) {return x&-x;}
11 void add(int u,int x) {for(; u<N; u+=lb(u))c[u]+=x;}
12 int get(int u) {int ret=0; for(; u; u-=lb(u))ret+=c[u]; return ret;}
13 int main() {
14     scanf("%d",&n);
15     for(int i=0; i<n; ++i)scanf("%d",&a[i].x),a[i].y=i+1;
16     sort(a,a+n);
17     ll ans=0;
18     for(int i=0,j,k; i<n; i=j) {
19         for(j=i; j<n&&a[j].x==a[i].x; ++j);
20         for(k=i; k<j; ++k) {
21             int t=get(a[k].y);
22             ans+=min(t,i-t);
23         }
24         for(k=i; k<j; ++k)add(a[k].y,1);
25     }
26     printf("%lld\n",ans);
27     return 0;
28 }

方法二:通过观察可以发现:

1.把一个元素通过相邻交换的方式移动到其他地方,其他元素的相对位置不变

2.对于单峰序列,比某个元素大的数必定都在该元素的同侧

因此,对于每个元素而言,要么把它左边比它大的元素都移到右边去,要么把它右边比它大的元素都移到左边来,因此每个元素对答案的贡献为min(左边比它大的元素数,右边比它大的元素数),树状数组左右各扫一遍就行了。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int N=1e5+10;
 5 int L[N],R[N],c[N],n,a[N];
 6 int lb(int x) {return x&-x;}
 7 void add(int u,int x) {for(; u<N; u+=lb(u))c[u]+=x;}
 8 int get(int u) {int ret=0; for(; u; u-=lb(u))ret+=c[u]; return ret;}
 9 int main() {
10     scanf("%d",&n);
11     for(int i=0; i<n; ++i)scanf("%d",&a[i]);
12     for(int i=0; i<n; ++i)L[i]=i-get(a[i]),add(a[i],1);
13     memset(c,0,sizeof c);
14     for(int i=n-1; i>=0; --i)R[i]=n-1-i-get(a[i]),add(a[i],1);
15     ll ans=0;
16     for(int i=0; i<n; ++i)ans+=min(L[i],R[i]);
17     printf("%lld\n",ans);
18     return 0;
19 }

猜你喜欢

转载自www.cnblogs.com/asdfsag/p/11394132.html