bzoj4240有趣的家庭菜园(贪心+逆序对)

对家庭菜园有兴趣的JOI君每年在自家的田地中种植一种叫做IOI草的植物。JOI君的田地沿东西方向被划分为N个区域,由西到东标号为1~N。IOI草一共有N株,每个区域种植着一株。在第i个区域种植的IOI草,在春天的时候高度会生长至hi,此后便不再生长。
为了观察春天的样子而出行的JOI君注意到了IOI草的配置与预定的不太一样。IOI草是一种非常依靠阳光的植物,如果某个区域的IOI草的东侧和西侧都有比它高的IOI草存在,那么这株IOI草就会在夏天之前枯萎。换句话说,为了不让任何一株IOI草枯萎,需要满足以下条件:
对于任意2<=i<=N-1,以下两个条件至少满足一个:
1. 对于任意1<=j<=i-1,hj<=hi
2. 对于任意i+1<=j<=N,hk<=hi
IOI草是非常昂贵的,为了不让IOI草枯萎,JOI君需要调换IOI草的顺序。IOI草非常非常的高大且纤细的植物,因此JOI君每次只能交换相邻两株IOI草。也就是说,JOI君每次需要选择一个整数i(1<=i<=N-1),然后交换第i株IOI草和第i+1株IOI草。随着夏天临近,IOI草枯萎的可能性越来越大,因此JOI君想知道让所有IOI草都不会枯萎的最少操作次数。
现在给出田地的区域数,以及每株IOI草的高度,请你求出让所有IOI草的不会枯萎的最少操作次数。
Solution
好恶心。。。
问题的模型非常常见,求交换次数。
如果做过关于逆序对的题的话,我们知道,对于一个数列下标1...n,把它进行操作后得到的下标为241....那么交换次数即为这个新数列的逆序对数。
现在问题来了,我们要把这个序列变成单峰的,求最少逆序对数。
看了网上一大堆题解,看的我是一脸懵逼头皮发麻。
我们如果把数列按照权值从小到大依次加入,那么这个数只有两种选择,走到最左边或者走到最右边。那对于这两种情况,我们取个min就好了。
但是数列中会有重复的数字,然后不会做了。
那么继续从逆序对的角度来考虑,我们把数列从大到小依次加入,那么之前加进去的数都是比它大的,那么这个数要么和左边比它大的数交换,要么和右边比它大的数交换,我们最后取个min。
这样的话对于重复的数字,我们可以考虑先把这个权值的答案统计好之后,再把这个权值的所有数加入树状数组中。
具体实现看代码。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 300002
using namespace std;
int tr[N],n;
long long ans;
struct zzh{
    int a,b;
}a[N];
bool cmp(zzh a,zzh b){
    return a.a>b.a;
}
inline int q(int x){int ans=0;while(x)ans+=tr[x],x-=x&-x;return ans;}
inline void add(int x){while(x<=n)tr[x]++,x+=x&-x;}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&a[i].a),a[i].b=i;
    sort(a+1,a+n+1,cmp);int now=1;
    for(int i=1;i<=n;i++){
        if(a[i].a!=a[i-1].a){
           for(;now<i;++now)add(a[now].b);
        }
        int x=q(a[i].b);
        ans+=min(now-1-x,x);
    }
    cout<<ans<<endl;
    return 0;
}

 

posted @ 2018-09-25 09:43  comld  阅读(367)  评论(0编辑  收藏  举报