杂讲002 一道小巧思的题目

P1966 NOIP 2013 提高组 火柴排队

这道题的思维相当之巧妙,首先,我们根据高中数学知识可以知晓 顺序乘积和>乱序乘积和,也就是排序不等式

那么,这道题就可以简单转化成一个调整顺序的问题,也就是,将两者的排名顺序调成一致。

先给出结论,就是这道题的答案就等于映射序列的逆序对数,下面来解释一下这个结论。

首先解释映射规则:a序列中每一个数都有一个排名序列,举个例子,比如现在n=4,a4=1 6 2 9,那么此时排序序列就是1 3 2 4,也就是该数列内值的排名重新整合为一个序列。我们要做的就是整理出a序列和b序列的排名序列,然后,重点来了,将a序列的排名的物理位置映射到b序列的排序序列上。用上方的数据做例子,比如说现在n=4,a4=1 6 2 9,b4=3 1 9 2,那么整理后的排名序列A4=1 3 2 4,B4=3 1 4 2,此时也就是以排名为纽带,将A的位置映射到B上,此时产生p数组,也就是p4=2 1 4 3这就是将A的排名的物理位置映射到B序列上得到的调整操作序列。此时,完成映射之后,可以将a序列的重排名序列视作1 2 3 4,p序列为b序列的重排名序列,我们要做的就是求p的逆序对数并输出。

下面解释一下为什么这样映射是合法的

  首先,对于两个序列来说,我们要将他们的排名调整到相同的顺序,从而保证所求和最小,这就涉及到相对位置。直接思考怎么做到这种效果,最简单的就是直接将两个序列都排序一遍,显然这样操作是可以将排名顺序调整到一致的。然而,这样做在只能交换相邻位置的数的情况下复杂度是完全不可接受的,在这道题里也肯定不是最优策略。那么我们现在可以这样想,因为我们只能交换相邻位置的数,那么就存在一个绝对位置和相对位置的问题。绝对位置在本题中是不确定的,不能通过单一策略直接得到。但是我们思考可以知道,相对位置,也就是排名之间的顺序是有用的。如果两个排名之间的相对顺序(3在4前)是正确的,那我们如果将二者调换,就还是要调换回去,这样子操作显然会增加代价。所以我们可以得到一个很好的策略,就是无论如何变动,如果元素相对位置是正确的,就不调换两者的相对位置,保证他们始终不会相互调换位置,这样就能够降低调换位置的代价。那么这与上方的映射有什么关系呢?先考虑有序序列的条件,假设a是一个有序(正序)的序列,那么非常显然,根据我们上方策略,如果b序列的排名顺序符合正序(若排名3的元素在排名4的元素之前,就坚决不调换两者的位置),我们可以发现,这种情况下,显然,最终调换的答案就是b序列的逆序对数。尝试推广一下,假设a序列是一个乱序的序列,排名是不确定的,那么如果a中排名为5的元素在排名为4的元素前,那么如果b序列中排名为5的元素在排名为4的元素之前,就坚决不调换这两者元素的相对位置。由此,我们得到了一个通用的规则。就是"b序列顺从a序列",如果b序列中排名的性质和a一样,就坚决不调换两者的相对位置。通过这一策略,我们假设现在a序列是一个正则的序列,也就是人为认定a序列的顺序一开始就是正确的,是正则的,所以我们就对b序列进行映射,求出b序列相对于a序列的相对位置序列,也就是上方映射规则得到的序列。对该序列求逆序对,就是最终答案。

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,mod=1e8-3;
long long ans=0,n,c[N];
void add(int x,int y,int n)
{
    for(;x<=n;x+=(x&-x)) c[x]+=y;
}
long long query(int x)
{
    long long ans=0;
    for(;x;x-=(x&-x)) ans+=c[x];
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin>>n;
    vector<int>a(n),b(n);
    for(int i=0;i<n;i++)
        cin>>a[i];
    for(int i=0;i<n;i++)
        cin>>b[i];
    vector<int>A=a;vector<int>B=b;
    sort(A.begin(),A.end());sort(B.begin(),B.end());
    vector<int> ranka(n+1),rankb(n+1);
    for(int i=0;i<n;i++)
    {
        ranka[i]=int(lower_bound(A.begin(),A.end(),a[i])-A.begin())+1;
        rankb[i]=int(lower_bound(B.begin(),B.end(),b[i])-B.begin())+1;
    }
    vector<int>pos(n+1);
    for(int i=0;i<n;i++)
        pos[ranka[i]]=i+1;
    vector<int>p(n);
    for(int i=0;i<n;i++)
        p[i]=pos[rankb[i]];
    for(int i=0;i<n;i++)
    {
        int left_rank=query(p[i]);
        ans+=i-left_rank;
        ans%=mod;
        add(p[i],1,n);
    }
    cout<<ans%mod;
    return 0;
}