【Usaco 2010 NOV Gold】奶牛的图片

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(归并版)

#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[N],t[N*3],b[N],ans,i,j,tot,c[N];
void merge(ll l,ll r,ll a[N]){
    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(树状数组)

#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[N],t[N*3],b[N],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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: p109 [noip2004 提高组] 合并果子: 这道题目是一道经典的贪心算法题目,题目大意是给定n个果子,每个果子的重量为wi,现在需要将这n个果子合并成一个果子,每次合并需要消耗的代价为合并的两个果子的重量之和,求最小的代价。 我们可以使用贪心算法来解决这个问题,每次选择两个最小的果子进行合并,然后将合并后的果子的重量加入到集合,重复这个过程直到只剩下一个果子为止。 这个算法的正确性可以通过反证法来证明,假设存在一种更优的合并方案,那么这个方案一定会在某一步将两个比当前选择的两个更小的果子进行合并,这样就会得到一个更小的代价,与当前选择的方案矛盾。 usaco06nov fence repair: 这道题目是一道经典的贪心算法题目,题目大意是给定n个木板,每个木板的长度为li,现在需要将这n个木板拼接成一块长度为L的木板,每次拼接需要消耗的代价为拼接的两个木板的长度之和,求最小的代价。 我们可以使用贪心算法来解决这个问题,每次选择两个最小的木板进行拼接,然后将拼接后的木板的长度加入到集合,重复这个过程直到只剩下一个木板为止。 这个算法的正确性可以通过反证法来证明,假设存在一种更优的拼接方案,那么这个方案一定会在某一步将两个比当前选择的两个更小的木板进行拼接,这样就会得到一个更小的代价,与当前选择的方案矛盾。 ### 回答2: 题目描述: 有n个果子需要合并,合并任意两个果子需要的代价为这两个果子的重量之和。现在有一台合并机器,可以将两个果子合并成一堆并计算代价。问将n个果子合并成一堆的最小代价。 这个问题可以用贪心算法来解决,我们可以使用一个最小堆来存储所有果子的重量。每次从最小堆取出两个最小的果子,将它们合并成为一堆,并将代价加入答案,将新堆的重量加入最小堆。重复以上步骤,直到最小堆只剩下一堆为止。这样得到的代价就是最小的。 证明如下: 假设最小堆的果子按照重量从小到大依次为a1, a2, ..., an。我们按照贪心策略,每次都将重量最小的两个果子合并成为一堆,设合并的过程为b1, b2, ..., bn-1。因此,可以发现,序列b1, b2, ..., bn-1必然是一个前缀和为a1, a2, ..., an的 Huffman 树变形。根据哈夫曼树的定义,这个树必然是最优的,能够得到的代价最小。 因此,使用贪心策略得到的答案必然是最优的,而且时间复杂度为O(n log n)。 对于[usaco06nov] fence repair g这道题,其实也可以用相同的思路来解决。将所有木板的长度存储在一个最小堆,每次取出最小的两个木板长度进行合并,代价即为这两个木板的长度之和,并将合并后木板的长度加入最小堆。重复以上步骤,直到最小堆只剩下一块木板。得到的代价就是最小的。 因此,贪心算法是解决这类问题的一种高效、简单但有效的方法,可以应用于很多有贪心性质的问题。 ### 回答3: 这两个题目都需要对操作进行模拟。 首先是合并果子。这个题目先将所有果子放进一个优先队列。每次取出来两个果子进行合并,直到只剩下一个果子即为答案。合并的代价为两个果子重量之和。每次合并完之后再将新的果子放入优先队列,重复上述过程即可。 再来看fence repair。这个题目需要用到贪心和并查集的思想。首先将所有板子的长度放入一个最小堆,每次取出堆顶元素即为最短的板子,将其与其相邻的板子进行合并,合并的长度为这两块板子的长度之和。操作完之后再将新的板子长度放入最小堆,重复上述过程直到只剩下一块板子。 关于合并操作,可以使用并查集来实现。维护每个板子所在的集合,每次操作时合并两个集合即可。 最后,需要注意的是题目给出的整数都很大,需要使用long long来存储避免溢出。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值