您的位置:首页 > 编程语言 > Go语言

【Usaco 2010 NOV Gold】奶牛的图片

2016-01-24 17:22 211 查看

Description

Farmer John希望给他的N(1<=N<=100,000)只奶牛拍照片,这样他就可以向他的朋友炫耀他的奶牛.这N只奶牛被标号为1..N.

在照相的那一天,奶牛们排成了一排.其中第i个位置上是标号为c_i(1<=c_i<=N)的奶牛.对于奶牛的站位,Farmer John有他自己的想法.

FJ是这么想的,标号为i(1<=i<=n-1)的奶牛只能站在标号为i+1的奶牛的左边,而标号为N的奶牛只能站在标号为1的奶牛的左边.当然,没有牛可以站在队列中最左边的奶牛的左边了.也就是说,最左边的奶牛编号是随意的.

这些奶牛都非常的饿,急切的希望吃到FJ承诺的在拍照后的大餐,所以FJ想尽快的拍照.奶牛们的方向感非常的不好,所以FJ每一分钟只可以选择相邻的两只奶牛然后让他们交换位置.FJ最小需要多少时间就能使奶牛站成一个可以接受的序列?

比方说一个有5只奶牛的例子,一开始序列是这样的:

左边 右边

3 5 4 2 1

第一分钟,FJ可以交换第二队奶牛(即5和4),交换后的队列:

3 4 5 2 1

第二分钟,FJ交换最右边的一对,序列变成这样:

3 4 5 1 2

这样,只用了2分钟,就是序列变为了一个FJ所希望的序列.

分析

对于这道题,我们其实是很难入手的,而这时我们先想想普通的暴力是什么?

最普通的是将每个1..n的序列求出来,然后求答案,但是这是O(n^2),而且很难优化。

所以我们可以先考虑特殊情况:

一个1..n的乱序的序列变成一个有序的序列,我们很容易想到是求这个序列的逆序对的个数。

而对此以外,好像没有什么其他可以利用的信息。

但是认真分析,其实是有关联的:

对于3 5 4 2 1 -> 1 2 3 4 5 逆序对:ans=8

然后3 5 4 2 1 -> 2 3 4 5 1 逆序对:ane=?

……

这时我们可不可以也把2 3 4 5 1看成有序的呢?其实是可以的,那么就是2 3 4 5 max

前面的序列就是3 4 5 2 max,这时的逆序对与上面的那个序列实际上来说就是

上面的序列除去1后产生的值加上max会造成的逆序对的数量。

也就是ane=8-4+0。再分析这样推下去是可以推再下层的,为什么?

关键是因为我们都是对于上一层的答案进行改变,而且每次改变都是改最小的那个变成最大的。

有人可能会问第一次是操作最小的,那第二次的2不是>1吗?但是1已经变成了max,所以2是此时的最小的。

所以我们可以总结成ane=ans-(p[i]-1)+(n-p[i]),p[i]表示数字i的位置。

说真的,这题真的很妙,很巧!于是我趁这次机会研究了一下逆序对的问题

首先,为什么逆序对的组数等于原序列交换成从小到大的最小次数呢?

伪证明1:

首先有ai>aj且i<=j,那么若想把这序列改成排序后的序列,那么这两个数必须交换,即次数必会加1.

再或者这样理解:每次交换一对数,一定会使逆序对数目加或减1,而最后的序列的逆序对数为0,所以最优解都是-1.

伪证明2:

与排序过的序列以外的序列都被定义为没排序的,而这样必定会有逆序对。

其次为什么一定可以交换逆序对的组数呢?(即相邻的交换)

这个问题留给读者。

而逆序对的计算可以用树状数组或者归并排序。

原谅我太懒了,只能放个标程了。。大家凑合理解吧。

代码1(归并版)

[code]#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const int N=100005;
ll n,a
,t[N*3],b
,ans,i,j,tot,c
;
void merge(ll l,ll r,ll a
){
    if (l>=r) return;
    int mid=(l+r)>>1;
    merge(l,mid,a);
    merge(mid+1,r,a);
    i=l;j=mid+1;
    tot=0;
    while(i<=mid&&j<=r){
        if(a[i]>a[j]) {
            c[++tot]=a[j];
            ans+=mid-i+1;
            j++;
        }else {
            c[++tot]=a[i];
            i++;
        }
    }
    for(int q=i;q<=mid;q++) c[++tot]=a[q];
    for(int p=j;p<=r;p++) c[++tot]=a[p];
    for(int i=l,j=1;i<=r;i++,j++) a[i]=c[j];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[a[i]]=i;
    }
    merge(1,n,a);
    ll sum,dat;
    dat=ans;
    sum=ans;
    for(int i=1;i<=n;i++){
        sum-=(b[i]-1);sum+=(n-b[i]);
        dat=min(dat,sum);
    }
    printf("%lld",dat);
}


代码2(树状数组)

[code]#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const int N=100005;
ll n,a
,t[N*3],b
,ans;
void find(int k,int l,int r,int x,int y){
    if (l==x&&r==y) ans+=t[k];
    else {
        int mid=(l+r)>>1;
        if (mid<x) find(k*2+1,mid+1,r,x,y);else 
        if (mid>=y) find(k*2,l,mid,x,y);else {
            find(k*2,l,mid,x,mid);
            find(k*2+1,mid+1,r,mid+1,y);
        }
    }
}
void insert(int k,int l,int r,int x){
    if (l==r) t[k]++;
    else {
        int mid=(l+r)>>1;
        if (mid<x) insert(k*2+1,mid+1,r,x);
        else insert(k*2,l,mid,x);
        t[k]=t[k*2]+t[k*2+1];
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[a[i]]=i;
        find(1,1,n,a[i],n);
        insert(1,1,n,a[i]);
    }
    ll sum,dat;
    dat=ans;
    sum=ans;
    for(int i=1;i<=n;i++){
        sum-=(b[i]-1);sum+=(n-b[i]);
        dat=min(dat,sum);
    }
    printf("%lld",dat);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: